DEV Community

Stephanie Opala
Stephanie Opala

Posted on

How to build a multi-step form using React.js + Material UI

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

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 
Enter fullscreen mode Exit fullscreen mode
cd multistep-form 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

Next, install Formik and Yup for form handling and validation.

npm install formik yup 
Enter fullscreen mode Exit fullscreen mode

Start the project on localhost.

npm start 
Enter fullscreen mode Exit fullscreen mode

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; 
Enter fullscreen mode Exit fullscreen mode

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); 
Enter fullscreen mode Exit fullscreen mode

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> 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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 
Enter fullscreen mode Exit fullscreen mode

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; 
Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
devopsking profile image
UWABOR KING COLLINS

nice post but live view will be appreciated

Collapse
 
mehdirh profile image
Mehdi Abdelaziz Rahal

Any live demo !!