Reputation: 25
I'm setting up a multi-step form to handle sign in with Next.js and Material-UI. In the signup.js
page located in pages/signup.js
. My multi-step form is created. Within this page, the child components to be displayed as the step content are defined in getStepContent(step)
and the main component is called SignInSide
below it. The steps for forward and back is also defined on the signup.js
page. The child form components only contain inputs. I am trying to pass the data entered into the child component form inputs to the parent component's state within the sign up page, signup.js
so I can submit it. Both the parent and the child component are functional components using react hooks for state.
I've already tried to pass props from the parent to the child component and then define the 'value' of the TextField components to {props.value} and that worked when I tested it with a console.log of value in the parent component I saw the letters coming through, however there was no name: attached to it, it was just coming through as just letters so I tried to add [name]: event.target.value but then when I try to write text in the input field it just says [Object object] however, in the console I can see [Object object](insert letter typed here) so its logging the text but not properly and the text typed is not visible in the input box.
This is where the step content is.
const steps = ['Welcome', 'Information', 'Creative', 'Confirm'];
function getStepContent(step) {
const [value, setValue] = React.useState('');
//set state for returned input values
function handleChange(newValue) {
setValue(newValue);
}
switch (step) {
case 0:
return (
<div style={{ padding: '8em 0 4em' }}>
<Hero title='Welcome' paragraph="Let's get you signed up." />
</div>
);
case 1: // Form Components
return <CredentialsForm value={value} onChange={handleChange} />;
case 2:
return <CreativeForm />;
case 3:
return <Review />;
default:
throw new Error('Unknown step');
}
}
// Sign In Page
export default function SignInSide() {
const classes = useStyles();
const [activeStep, setActiveStep] = React.useState(0);
const [form, setForm] = React.useState(false);
// Forward and Back button functions, the buttons are defined here, in the parent page component.
const handleNext = () => {
setActiveStep(activeStep + 1);
};
const handleBack = () => {
setActiveStep(activeStep - 1);
};
const handleFormExtend = () => {
setForm(true);
setActiveStep(activeStep + 1);
};
return (
...
)
}
The parent component is SignInSide
. Im using material-ui. An example of the stepper form im using can be seen here: https://material-ui.com/getting-started/templates/checkout/ its similar code, I just changed the textfields to outlined and set up the logic for the props passed from the parent.
Inside <CredentialsForm value={value} onChange={handleChange} />
component.
export default function CredentialsForm(props) {
const classes = useStyles();
// Floating Form Label
const inputLabel = React.useRef(null);
const [labelWidth, setLabelWidth] = React.useState(0);
React.useEffect(() => {
setLabelWidth(inputLabel.current.offsetWidth);
}, []);
//Send input value to parent state
const handleChange = name => event => {
props.onChange({
[name]: event.target.value
});
};
return (
<div className={classes.paper}>
<Avatar className={classes.avatar}>
<LockOutlinedIcon />
</Avatar>
<Typography component='h1' variant='h5'>
Who Are U?
</Typography>
<form className={classes.form} noValidate>
<Grid container spacing={2}>
<Grid item xs={2} md={2}>
<FormControl
margin='normal'
variant='outlined'
fullWidth
required
className={classes.formControl}
>
<InputLabel
ref={inputLabel}
htmlFor='outlined-userTitle-native-simple'
>
Title
</InputLabel>
<Select
native
value={props.value}
onChange={handleChange('title')}
labelWidth={labelWidth}
required
inputProps={{
name: 'title'
}}
>
<option value='' />
<option value={10}>Mr.</option>
<option value={20}>Ms.</option>
<option value={20}>Mrs.</option>
<option value={30}>Non-Binary</option>
</Select>
</FormControl>
</Grid>
<Grid item xs={5} md={5}>
<TextField
variant='outlined'
margin='normal'
required
fullWidth
value={props.value}
onChange={handleChange('firstName')}
id='first'
label='First Name'
name='firstname'
/>
</Grid>
</Grid>
</form>
</div>
);
}
Expected: On user input the child component saves the input in state and passes it to the parent component so it can submit the data for sign up.
Actual: input sucessfully being passed to parent, but in the wrong format. It is passing as "[Object object](user typed letters here)" and in the input field the user can only see [Object object] as seen in the Console. No errors.
Upvotes: 2
Views: 5338
Reputation: 14355
Your problem is here:
const handleChange = name => event => {
props.onChange({
[name]: event.target.value
});
};
You're giving your props.onChange
function an object, but that function is updating a state hook that is initialized with a string value. Then you set the text value to an object, when it used to be a string giving your odd looking object text.
In addition, you are setting all your form values to the same string from the parent state.
To resolve this, make your state an object instead of a string.
In parent:
const [values, setValues] = React.useState({title: '', firstName: ''});
....
function handleChange(newValue) {
setValues({...values, ...newValue});
}
....
return <CredentialsForm values={values} onChange={handleChange} />;
In child:
<Select
native
value={props.values.title} // Change to this
onChange={handleChange('title')}
labelWidth={labelWidth}
required
inputProps={{
name: 'title'
}}
>
....
<TextField
variant='outlined'
margin='normal'
required
fullWidth
value={props.values.firstName} // Change to this
onChange={handleChange('firstName')}
id='first'
label='First Name'
name='firstname'
/>
Recommended but not required: I'd get rid of the handleChange
function in the form component since it doesn't really add value. Then call props.onChange
directly from the input, and change your parent handleChange
to:
const handleChange = name => event => {
setValues({...values, [name]: event.target.value});
}
Upvotes: 5