I'm building an application, where there is a form presented with different steps. In all the steps but one, I manage to provide the necessary functions as props to make some operations such as 'handleNext', 'handleBack' or 'handleChange'.
Nevertheless, in the last step, represented in the class SuccessForm, when I try to execute the 'handleDownload' function, I get the following error:
TypeError: this.props.handleDownload is not a function
Here it is the SuccessForm.js class:
export class SuccessForm extends Component {
constructor() {
super();
}
download = e => {
e.preventDefault();
this.props.handleDownload();
}
render() {
return (
<React.Fragment>
<Grid container>
<Grid item xs={12} sm={2}>
<DirectionsWalkIcon fontSize="large" style={{
fill: "orange", width: 65,
height: 65
}} />
</Grid>
<Grid>
<Grid item xs={12} sm={6}>
<Typography variant="h5" gutterBottom>
Route created
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="subtitle1">
Your new track was succesfully created and saved
</Typography>
</Grid>
</Grid>
<Tooltip title="Download" arrow>
<IconButton
variant="contained"
color="primary"
style={{
marginLeft: 'auto',
// marginRight: '2vh'
}}
onClick={this.download}
>
<GetAppIcon fontSize="large" style={{ fill: "orange" }} />
</IconButton>
</Tooltip>
</Grid>
</React.Fragment>
)
}
}
The entire NewRouteForm.js:
import React, { Component } from 'react'
import { makeStyles, MuiThemeProvider } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Stepper from '@material-ui/core/Stepper';
import Step from '@material-ui/core/Step';
import StepLabel from '@material-ui/core/StepLabel';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import DataForm from '../stepper/dataform/DataForm';
import ReviewForm from '../stepper/reviewform/ReviewForm';
import MapForm from '../stepper/mapform/MapForm';
import NavBar from '../../graphic interface/NavBar';
import DirectionsWalkIcon from '@material-ui/icons/DirectionsWalk';
import Avatar from '@material-ui/core/Avatar';
import CheckCircleOutlineOutlinedIcon from '@material- ui/icons/CheckCircleOutlineOutlined';
import FilterHdrIcon from '@material-ui/icons/FilterHdr';
import Grid from '@material-ui/core/Grid';
import SuccessForm from '../stepper/success/SuccessForm';
import { withStyles } from '@material-ui/styles';
export class NewRouteForm extends Component {
state = {
activeStep: 0,
name: '',
description: '',
date: new Date(),
photos: [],
videos: [],
points: []
};
handleNext = () => {
const { activeStep } = this.state;
this.setState({ activeStep: activeStep + 1 });
};
handleBack = () => {
const { activeStep } = this.state;
this.setState({ activeStep: activeStep - 1 });
};
handleChange = input => e => {
this.setState({ [input]: e.target.value });
}
handleDateChange = date => {
this.setState({ date: date });
}
handleMediaChange = (selectorFiles: FileList, code) => { // this is not an error, is TypeScript
switch (code) {
case 0: // photos
this.setState({ photos: selectorFiles });
break;
case 1: // videos
this.setState({ videos: selectorFiles });
break;
default:
alert('Invalid media code!!');
console.log(code)
break;
}
}
handleMapPoints = points => {
this.setState({ points: points })
}
// ###########################
// Download and Upload methods
// ###########################
handleDownload = () => {
// download route
console.log("DOWNLOAD")
alert("DOWNLOAD");
}
upload = () => {
// upload route
}
render() {
const { activeStep } = this.state;
const { name, description, date, photos, videos, points } = this.state;
const values = { activeStep, name, description, date, photos, videos, points };
const { classes } = this.props;
return (
<MuiThemeProvider>
<React.Fragment>
<NavBar />
<main className={classes.layout}>
<Paper className={classes.paper}>
<Avatar className={classes.avatar}>
<FilterHdrIcon fontSize="large" />
</Avatar>
<Typography ponent="h1" variant="h4" align="center">
Create your own route
</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<React.Fragment>
{activeStep === steps.length ? (
<SuccessForm />
) : (
<React.Fragment>
{getStepContent(activeStep,
values,
this.handleNext,
this.handleBack,
this.handleChange,
this.handleDateChange,
this.handleMediaChange,
this.handleMapPoints,
this.handleDownload
)}
</React.Fragment>
)}
</React.Fragment>
</Paper>
</main>
</React.Fragment>
</MuiThemeProvider>
)
}
}
const steps = ['Basic data', 'Map', 'Review your route'];
function getStepContent(step,
values,
handleNext,
handleBack,
handleChange,
handleDateChange,
handleMediaChange,
handleMapPoints,
handleDownload) {
switch (step) {
case 0:
return <DataForm
handleNext={handleNext}
handleChange={handleChange}
handleDateChange={handleDateChange}
handleMediaChange={handleMediaChange}
values={values}
/>;
case 1:
return <MapForm
handleNext={handleNext}
handleBack={handleBack}
handleMapPoints={handleMapPoints}
values={values}
/>;
case 2:
return <ReviewForm
handleNext={handleNext}
handleBack={handleBack}
values={values}
/>;
case 3:
return <SuccessForm
handleDownload={handleDownload}
/>;
default:
throw new Error('Unknown step');
}
}
const useStyles = theme => ({
layout: {
width: 'auto',
marginLeft: theme.spacing(2),
marginRight: theme.spacing(2),
[theme.breakpoints.up(600 + theme.spacing(2) * 2)]: {
width: 600,
marginLeft: 'auto',
marginRight: 'auto',
},
},
paper: {
marginTop: theme.spacing(3),
marginBottom: theme.spacing(3),
padding: theme.spacing(2),
[theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
marginTop: theme.spacing(6),
marginBottom: theme.spacing(6),
padding: theme.spacing(3),
},
},
stepper: {
padding: theme.spacing(3, 0, 5),
},
buttons: {
display: 'flex',
justifyContent: 'flex-end',
},
button: {
marginTop: theme.spacing(3),
marginLeft: theme.spacing(1),
},
avatar: {
marginLeft: 'auto',
marginRight: 'auto',
backgroundColor: theme.palette.warning.main,
},
icon: {
width: 65,
height: 65,
},
grid: {
marginLeft: theme.spacing(2),
}
});
export default withStyles(useStyles)(NewRouteForm);
I'm building an application, where there is a form presented with different steps. In all the steps but one, I manage to provide the necessary functions as props to make some operations such as 'handleNext', 'handleBack' or 'handleChange'.
Nevertheless, in the last step, represented in the class SuccessForm, when I try to execute the 'handleDownload' function, I get the following error:
TypeError: this.props.handleDownload is not a function
Here it is the SuccessForm.js class:
export class SuccessForm extends Component {
constructor() {
super();
}
download = e => {
e.preventDefault();
this.props.handleDownload();
}
render() {
return (
<React.Fragment>
<Grid container>
<Grid item xs={12} sm={2}>
<DirectionsWalkIcon fontSize="large" style={{
fill: "orange", width: 65,
height: 65
}} />
</Grid>
<Grid>
<Grid item xs={12} sm={6}>
<Typography variant="h5" gutterBottom>
Route created
</Typography>
</Grid>
<Grid item xs={12}>
<Typography variant="subtitle1">
Your new track was succesfully created and saved
</Typography>
</Grid>
</Grid>
<Tooltip title="Download" arrow>
<IconButton
variant="contained"
color="primary"
style={{
marginLeft: 'auto',
// marginRight: '2vh'
}}
onClick={this.download}
>
<GetAppIcon fontSize="large" style={{ fill: "orange" }} />
</IconButton>
</Tooltip>
</Grid>
</React.Fragment>
)
}
}
The entire NewRouteForm.js:
import React, { Component } from 'react'
import { makeStyles, MuiThemeProvider } from '@material-ui/core/styles';
import Paper from '@material-ui/core/Paper';
import Stepper from '@material-ui/core/Stepper';
import Step from '@material-ui/core/Step';
import StepLabel from '@material-ui/core/StepLabel';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import DataForm from '../stepper/dataform/DataForm';
import ReviewForm from '../stepper/reviewform/ReviewForm';
import MapForm from '../stepper/mapform/MapForm';
import NavBar from '../../graphic interface/NavBar';
import DirectionsWalkIcon from '@material-ui/icons/DirectionsWalk';
import Avatar from '@material-ui/core/Avatar';
import CheckCircleOutlineOutlinedIcon from '@material- ui/icons/CheckCircleOutlineOutlined';
import FilterHdrIcon from '@material-ui/icons/FilterHdr';
import Grid from '@material-ui/core/Grid';
import SuccessForm from '../stepper/success/SuccessForm';
import { withStyles } from '@material-ui/styles';
export class NewRouteForm extends Component {
state = {
activeStep: 0,
name: '',
description: '',
date: new Date(),
photos: [],
videos: [],
points: []
};
handleNext = () => {
const { activeStep } = this.state;
this.setState({ activeStep: activeStep + 1 });
};
handleBack = () => {
const { activeStep } = this.state;
this.setState({ activeStep: activeStep - 1 });
};
handleChange = input => e => {
this.setState({ [input]: e.target.value });
}
handleDateChange = date => {
this.setState({ date: date });
}
handleMediaChange = (selectorFiles: FileList, code) => { // this is not an error, is TypeScript
switch (code) {
case 0: // photos
this.setState({ photos: selectorFiles });
break;
case 1: // videos
this.setState({ videos: selectorFiles });
break;
default:
alert('Invalid media code!!');
console.log(code)
break;
}
}
handleMapPoints = points => {
this.setState({ points: points })
}
// ###########################
// Download and Upload methods
// ###########################
handleDownload = () => {
// download route
console.log("DOWNLOAD")
alert("DOWNLOAD");
}
upload = () => {
// upload route
}
render() {
const { activeStep } = this.state;
const { name, description, date, photos, videos, points } = this.state;
const values = { activeStep, name, description, date, photos, videos, points };
const { classes } = this.props;
return (
<MuiThemeProvider>
<React.Fragment>
<NavBar />
<main className={classes.layout}>
<Paper className={classes.paper}>
<Avatar className={classes.avatar}>
<FilterHdrIcon fontSize="large" />
</Avatar>
<Typography ponent="h1" variant="h4" align="center">
Create your own route
</Typography>
<Stepper activeStep={activeStep} className={classes.stepper}>
{steps.map((label) => (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<React.Fragment>
{activeStep === steps.length ? (
<SuccessForm />
) : (
<React.Fragment>
{getStepContent(activeStep,
values,
this.handleNext,
this.handleBack,
this.handleChange,
this.handleDateChange,
this.handleMediaChange,
this.handleMapPoints,
this.handleDownload
)}
</React.Fragment>
)}
</React.Fragment>
</Paper>
</main>
</React.Fragment>
</MuiThemeProvider>
)
}
}
const steps = ['Basic data', 'Map', 'Review your route'];
function getStepContent(step,
values,
handleNext,
handleBack,
handleChange,
handleDateChange,
handleMediaChange,
handleMapPoints,
handleDownload) {
switch (step) {
case 0:
return <DataForm
handleNext={handleNext}
handleChange={handleChange}
handleDateChange={handleDateChange}
handleMediaChange={handleMediaChange}
values={values}
/>;
case 1:
return <MapForm
handleNext={handleNext}
handleBack={handleBack}
handleMapPoints={handleMapPoints}
values={values}
/>;
case 2:
return <ReviewForm
handleNext={handleNext}
handleBack={handleBack}
values={values}
/>;
case 3:
return <SuccessForm
handleDownload={handleDownload}
/>;
default:
throw new Error('Unknown step');
}
}
const useStyles = theme => ({
layout: {
width: 'auto',
marginLeft: theme.spacing(2),
marginRight: theme.spacing(2),
[theme.breakpoints.up(600 + theme.spacing(2) * 2)]: {
width: 600,
marginLeft: 'auto',
marginRight: 'auto',
},
},
paper: {
marginTop: theme.spacing(3),
marginBottom: theme.spacing(3),
padding: theme.spacing(2),
[theme.breakpoints.up(600 + theme.spacing(3) * 2)]: {
marginTop: theme.spacing(6),
marginBottom: theme.spacing(6),
padding: theme.spacing(3),
},
},
stepper: {
padding: theme.spacing(3, 0, 5),
},
buttons: {
display: 'flex',
justifyContent: 'flex-end',
},
button: {
marginTop: theme.spacing(3),
marginLeft: theme.spacing(1),
},
avatar: {
marginLeft: 'auto',
marginRight: 'auto',
backgroundColor: theme.palette.warning.main,
},
icon: {
width: 65,
height: 65,
},
grid: {
marginLeft: theme.spacing(2),
}
});
export default withStyles(useStyles)(NewRouteForm);
Share
Improve this question
edited Mar 30, 2020 at 18:26
Pablo Cañal Suárez
asked Mar 30, 2020 at 17:51
Pablo Cañal SuárezPablo Cañal Suárez
2351 gold badge4 silver badges11 bronze badges
3
- okay, finally we need that NewDataForm.js file, that's the important one because there is where you define your functions – Edgar Henriquez Commented Mar 30, 2020 at 18:11
- @EdgarHenriquez done. Sorry for the confusion. – Pablo Cañal Suárez Commented Mar 30, 2020 at 18:17
- You also have a bug on the last condition... check the updated answer – Dennis Vash Commented Mar 30, 2020 at 18:21
2 Answers
Reset to default 4Try calling super(props)
in the constructor:
constructor(props) {
super(props);
}
and passing function with this
instance (this.handleDownload
) as it is a class property:
<SuccessForm handleDownload={this.handleDownload} />
Update:
You have a bug on the last step when you not passing a property:
activeStep === steps.length ? <SuccessForm handleDownload={this.handleDownload}/>
Assuming that you have a class in your parent Component, what you're missing is the this
keyword in the function reference...
case 3:
return <SuccessForm
handleDownload={this.handleDownload}
/>;