Forms are used often in applications to capture information from users. A multi-step form is a form where its content is grouped into various steps with smaller pieces of the content. These types of forms are mostly used in cases where the content on the form is a lot, therefore, breaking it into smaller sections improves the user experience.
This article covers the steps on how to make a multi-step form using React.js, Material UI, and Formik and Yup for form validation.
Table of contents
- Prerequisites
- Getting Started
- Creating the parent component
- Creating the child components
- Conclusion
Prerequisites
To follow along, you will need to have:
- Basic knowledge of React JS.
- Basic knowledge of Formik.
Getting Started
Create a new React project using the commands below in your terminal.
npx create-react-app multistep-form
cd multistep-form
Install Material UI for styling. This library also contains some components that we
will use to build the form.
npm install @mui/material @emotion/react @emotion/styled
Next, install Formik and Yup for form handling and validation.
npm install formik yup
Start the project on localhost.
npm start
Creating the parent component
Our form will have three steps. The first step will contain the account details such as email and password.
The second step will contain a user's personal information such as name, phone number, and residence. The last step
is a review step where all the information that the user has entered in the form is displayed before he/she submits the form. We will have a parent component Form
and three child components namely, AccountDetails
, PersonalInfo
and ReviewInfo
.
Navigate to the src
folder and create a folder named components
. Inside the components
folder, create a file, Form.jsx
.
This will be the parent component. Create three other files namely, AccountDetails.jsx
, PersonalInfo.jsx
and ReviewInfo.jsx
that will contain the child components. In Form.jsx
, add the following code.
import { useState } from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';
import {
Box,
Stepper,
Step,
StepLabel,
Grid,
FormHelperText,
Button
} from '@mui/material';
import PersonalInfo from './PersonalInfo';
import AccountDetails from './AccountDetails';
import ReviewInfo from './ReviewInfo';
const steps = [' Account Details', 'Personal Info', 'Review and Submit'];
const Form = () => {
const [activeStep, setActiveStep] = useState(0);
const handleBack = () => {
setActiveStep((prevStep) => prevStep - 1);
};
const formik = useFormik({
initialValues: {
email: '',
password: '',
confirmPassword: '',
firstName: '',
lastName: '',
phone: '',
residence: ''
},
validationSchema: Yup.object().shape({
email: Yup.string()
.required('Email is required')
.email('Invalid email'),
password: Yup.string()
.min(8),
confirmPassword: Yup.string()
.min(8)
.oneOf([Yup.ref('password')], 'Passwords do not match'),
firstName: Yup.string()
.required('First Name is required'),
lastName: Yup.string()
.required('Last Name is required'),
}),
onSubmit: () => {
if (activeStep === steps.length - 1) {
console.log('last step');
} else {
setActiveStep((prevStep) => prevStep + 1);
}
}
});
const formContent = (step) => {
switch(step) {
case 0:
return <AccountDetails formik={formik} />;
case 1:
return <PersonalInfo formik={formik} />;
case 2:
return <ReviewInfo formik={formik} />;
default:
return <div>404: Not Found</div>
}
};
return (
<Box
sx={{
maxWidth: '600px',
padding: 2
}}
>
<Stepper
activeStep={activeStep}
orientation="horizontal"
>
{steps.map((label, index) => (
<Step key={index}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
<Grid container>
<Grid
item
xs={12}
sx={{ padding: '20px' }}
>
{formContent(activeStep)}
</Grid>
{formik.errors.submit && (
<Grid
item
xs={12}
>
<FormHelperText error>
{formik.errors.submit}
</FormHelperText>
</Grid>
)}
<Grid
item
xs={12}
>
<Button
disabled={activeStep === 0}
onClick={handleBack}
>
Back
</Button>
{activeStep === steps.length - 1 ? (
<Button>
Submit
</Button>
) : (
<Button onClick={formik.handleSubmit}>
Next
</Button>
) }
</Grid>
</Grid>
</Box>
)
}
export default Form;
At the top of Form.jsx
, we import the useState
hook from react
. We then import the useFormik
hook and Yup for form handling and validation. Next, we import Material UI components and lastly, the child components.
After the imports, we declare a variable steps
that is an array of strings with the names of our steps in the form.
The Form
component returns JSX which includes the <Stepper></Stepper>
material UI component. For this project, we will use a horizontal linear stepper. A linear stepper allows the user to complete the steps in sequence. This component accepts several props which include activeStep
, orientation
etc. The activeStep
prop is a zero-based index. In our code, we initialize the state activeStep
and set it to 0
.
const [activeStep, setActiveStep] = useState(0);
We then pass it as props to the Stepper
component.
<Stepper
activeStep={activeStep}
orientation="horizontal"
>
{steps.map((label, index) => (
<Step key={index}>
<StepLabel>{label}</StepLabel>
</Step>
))}
</Stepper>
The orientation
prop is the layout flow direction. The value can either be horizontal
or vertical
. Inside the <Stepper></Stepper>
component we map through the steps
array and return a <Step></Step>
component for each item.
Below the <Stepper></Stepper>
, we have a grid container with grid items. The first grid item wraps around a function call, formContent
, that accepts the activeStep
prop and returns a switch statement with the respective child component. The other grid item wraps around the back and next/submit buttons. The back button is disabled if the activeStep
is equal to 0
, which means that the user is on the first step of the form.
To display the next or submit button, we have a ternary operator to check if the user is on the last step. If so, we display the submit button, if not, we display the next button. The back and submit buttons have an onClick
event handler that calls the handleBack
and handleSubmit
functions respectively.
Form.jsx
also contains our validation schema and we pass the formik
object to the child components as props.
Creating the child components
The three children components recieve the formik
object that has the form values, errors, and onSubmit
event listener among other properties.
AccountDetails.jsx
import {
Grid,
TextField,
FormHelperText
} from "@mui/material";
const AccountDetails = (props) => {
const { formik } = props;
return (
<Grid
container
spacing={2}
>
<Grid
item
xs={12}
>
<TextField
name="email"
label="Email"
variant="outlined"
type="email"
fullWidth
size="small"
error={Boolean(formik.touched.email && formik.errors.email)}
onChange={formik.handleChange}
value={formik.values.email}
/>
</Grid>
<Grid
item
xs={12}
>
<TextField
name="password"
label="Password"
variant="outlined"
size='small'
type="password"
fullWidth
error={Boolean(formik.touched.password && formik.errors.password)}
onChange={formik.handleChange}
value={formik.values.password}
/>
</Grid>
<Grid
item
xs={12}
>
<TextField
name="confirmPassword"
label="Confirm Password"
variant="outlined"
size="small"
type="password"
fullWidth
error={Boolean(formik.touched.confirmPassword && formik.errors.confirmPassword)}
onChange={formik.handleChange}
value={formik.values.confirmPassword}
/>
</Grid>
{formik.errors.submit && (
<Grid
item
xs={12}
>
<FormHelperText error>
{formik.errors.submit}
</FormHelperText>
</Grid>
)}
</Grid>
)
}
export default AccountDetails
PersonalInfo.jsx
import {
TextField,
Grid
} from '@mui/material';
const PersonalInfo = (props) => {
const { formik } = props;
return (
<Grid
container
spacing={2}
>
<Grid
item
xs={6}
>
<TextField
name="firstName"
label="First Name"
variant="outlined"
size='small'
fullWidth
value={formik.values.firstName}
onChange={formik.handleChange}
error={formik.touched.firstName && Boolean(formik.errors.firstName)}
helperText={formik.touched.firstName && formik.errors.firstName}
/>
</Grid>
<Grid
item
xs={6}
>
<TextField
name="lastName"
label="Last Name"
variant="outlined"
size="small"
fullWidth
value={formik.values.lastName}
onChange={formik.handleChange}
error={formik.touched.lastName && Boolean(formik.errors.lastNamel)}
helperText={formik.touched.lastName && formik.errors.lastName}
/>
</Grid>
<Grid
item
xs={12}
>
<TextField
name="phone"
label="Phone Number"
variant="outlined"
type="phone"
fullWidth
size="small"
value={formik.values.phone}
onChange={formik.handleChange}
error={formik.touched.phone && Boolean(formik.errors.phone)}
helperText={formik.touched.phone && formik.errors.phone}
/>
</Grid>
<Grid
item
xs={12}
>
<TextField
name="residence"
label="Residence"
variant="outlined"
size="small"
fullWidth
value={formik.values.residence}
onChange={formik.handleChange}
error={formik.touched.residence && Boolean(formik.errors.residence)}
helperText={formik.touched.residence && formik.errors.residence}
/>
</Grid>
</Grid>
)
}
export default PersonalInfo
ReviewInfo.jsx
This file will display the form values that the user entered in the form.
import {
Typography,
List,
ListItem,
ListItemText
} from '@mui/material';
const ReviewInfo = ({ formik }) => {
const { values } = formik;
return (
<>
<Typography variant="overline" >
Account Details
</Typography>
<List>
<ListItem>
<ListItemText
primary="Email"
secondary={values.email}
/>
</ListItem>
</List>
<Typography variant="overline">
Personal Information
</Typography>
<List>
<ListItem>
<ListItemText
primary="First Name"
secondary={values.firstName}
/>
</ListItem>
<ListItem>
<ListItemText
primary="Last Name"
secondary={values.lastName}
/>
</ListItem>
<ListItem>
<ListItemText
primary="Phone Number"
secondary={values.phone}
/>
</ListItem>
<ListItem>
<ListItemText
primary="Residence"
secondary={values.residence}
/>
</ListItem>
</List>
</>
)
}
export default ReviewInfo
Lastly, import the Form.jsx
file in App.jsx
.
import './App.css';
import Form from './components/Form';
const App = () => {
return (
<div className="App">
<Form />
</div>
);
}
export default App;
Conclusion
In this article, we have covered how to create a multi-step form using React, Material UI, and Formik and Yup for form handling and validation. If you would like to customize your form further, you can check the Material UI documentation on how you can do that using the Stepper
component.
Top comments (2)
nice post but live view will be appreciated
Any live demo !!