React – Material Form Validation with Formik

Table of Contents
Overview
This article will go over an example of form validation with Material UI inputs using Formik and Yup. The form will have a text, date, radio, and a checkbox that will be validated before the user can submit the form.
Download Source from GitHub https://github.com/fullstacksoup
GitHubGetting Started
Create the application
npx create react-app multi-step-form-demo
Material UI – Version 4
React-Material for the inputs and layout.
Note: Version 5 was released during the development of this article.
For version 4 documentation click here
For the current version documentation click here
All documentation for all versions here
npm install @material-ui/core@4.12.3 @material-ui/icons@4.11.2
npm install @material-ui/lab@4.0.0-alpha.60
npm install @material-ui/pickers@3.3.10
Material UI Phone Number
material-ui-phone-number is the phone number library that has a dropdown for regional codes.

npm i material-ui-phone-number
Material Stepper With Formik & Yup
This will be the parent component index.js will handle the multi step form UI logic and field validation for all the forms. The stepper form is take directly from Material UI’s website here if you want more details on stepper forms then go to https://mui.com/components/steppers/#main-content.
Each form will have a Yup validation schema that will be used by the corresponding Formik
Warning! This is a big source file and can be broken up into smaller ones, but it was easier to just have it in one file for demo purposes.
Library Imports
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import CssBaseline from '@material-ui/core/CssBaseline';
import Grid from '@material-ui/core/Grid';
import Paper from '@material-ui/core/Paper';
import { useFormik } from 'formik';
import * as yup from 'yup';
import { addDays } from 'date-fns'
import MaterialForm from 'components/form/MaterialForm';
Inline Styles
const useStyles = makeStyles((theme) => ({
appBar: {
position: 'relative',
},
layout: {
width: '1280px',
marginLeft: '11vw',
[theme.breakpoints.down(600 + theme.spacing(3) * 2)]: {
marginLeft: '1vw',
},
},
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),
},
}
}));
Yup and Formik
Yup Validation Schema
Each form has it’s own validation schema for the group of fields that need validation
.required() – Shows the message when user submits and the input is still pristine.

.email(), .min() – Shows the message when field is no longer pristine and there is input.

Personal Information Form
The personal information form will have three required fields (name, email, & phone) and one non-required field (location). Using the Yup .required() property the field will be validated.
const formValidationSchema = yup.object({
name: yup
.string('Enter your name')
.min(2, 'Name should be of minimum 2 characters length')
.required('Name is required'),
email: yup
.string('Enter your email')
.email('Enter a valid email')
.required('Email is required'),
phone: yup
.string('Enter your phone number')
.min(17, '10 Digit Phone Number')
.required('Phone is required'),
startDate: yup
.date()
.min(addDays(new Date(), 1))
.max(addDays(new Date(), 30))
.required('Date is required'),
color: yup
.mixed('Pick a color')
.required('Color is required'),
hasFaxNumber: yup
.string('Please choose Yes or No')
.required('Fax is required'),
faxNumber: yup
.string()
.when("hasFaxNumber", {
is: "Yes",
then: yup.string().required('Fax number is required if you answered Yes above').min(17, '10 Digit Phone Number')
}),
secondUsername: yup
.string('Name is required if request is one behalkf of another person')
.when("onBehalfOf", {
is: "Yes",
then: yup.string('Name is required if request is one behalkf of another person').required('Name is required if you answered Yes above')
})
});
Formik Form with useFormik() Hook
The useFormik() hook validates each form against the Yup validation schemas.
const formik = useFormik({
initialValues: {
name: '',
email: '',
phone: '',
startDate: null,
color: '',
hasFaxNumber: '',
faxNumber: ''
},
validationSchema: formValidationSchema,
onSubmit: values => {
handleFormSubmit();
},
});
Handle Submit
The handleSubmit() function executes the formik validations by the current step.
const handleFormSubmit = () => {
var data = {
Name: formik.values.name,
Email: formik.values.email,
Phone: formik.values.phone,
StartDate: formik.values.startDate,
Color: formik.values.color
}
alert(JSON.stringify(data, null, 2));
}
Render the Form
Traversing the step counter and the content rendered with handleNext(), handleBack(), and getStepCount(step).
return (
<React.Fragment>
<Grid container spacing={1}>
<Grid item xs={12} lg={3} xl={4}></Grid>
<Grid item xs={12} lg={6} xl={4}>
<Paper className={classes.paper}>
<form noValidate autoComplete="off">
<MaterialForm formik={formik}/>
</form>
</Paper>
</Grid>
<Grid item xs={12} lg={3} xl={4}></Grid>
</Grid>
</React.Fragment>
);
}
Form Component
This form displays the 5 fields for name, email, phone, date, and radio buttons. All of the fields are required.
Each input uses formik to handle onChange, onBlur to check if if the input is pristine, error to highlight the input in red, helperText for the error message as shown below.
onChange={props.formik.handleChange}
onBlur={props.formik.handleBlur}
value={props.formik.values.name}
error={props.formik.touched.name && Boolean(props.formik.errors.name)}
helperText={props.formik.touched.name && props.formik.errors.name}
The Entire Form Component
This component is imported from “.\components\form\MaterialForm.js” and rendered in the parent component “.\pages\form\index.js“. All of the fields on the form are required, thus have a .required() property in the Yup validation schema. This form has the following input fields:
- Name
- Phone number
- Date
- Radio Button
handleSubmit() will show the input as JSON data in an alert() popup.
Code for the Form
import React from 'react'
import Grid from '@material-ui/core/Grid';
import Container from '@material-ui/core/Container';
import TextField from '@material-ui/core/TextField';
import MuiPhoneNumber from "material-ui-phone-number";
import FormControl from '@material-ui/core/FormControl';
import Radio from '@material-ui/core/Radio';
import RadioGroup from '@material-ui/core/RadioGroup';
import Alert from '@material-ui/lab/Alert';
import FormControlLabel from '@material-ui/core/FormControlLabel';
import FormLabel from '@material-ui/core/FormLabel';
import styles from 'styles/Global.module.css';
import { Button, Typography } from '@material-ui/core';
export default function MaterialForm(props) {
return (
<>
<Container maxWidth="xs">
<Grid container spacing={3}>
<Grid item xs={12} >
<Typography variant="h4" align="center">
Form Validation
</Typography>
</Grid>
<Grid item xs={12} >
<TextField
fullWidth
variant="outlined"
size="small"
label="Name"
id="name"
name="name"
type="text"
inputProps={{style: {textTransform: 'capitalize'}}}
onChange={props.formik.handleChange}
onBlur={props.formik.handleBlur}
value={props.formik.values.name}
error={props.formik.touched.name && Boolean(props.formik.errors.name)}
helperText={props.formik.touched.name && props.formik.errors.name}
InputLabelProps={{
shrink: true,
}}
/>
</Grid>
<Grid item xs={12} >
<TextField
fullWidth
size="small"
variant="outlined"
label="Email"
id="email"
name="email"
type="email"
inputProps={{style: {textTransform: 'lowercase'}}}
onChange={props.formik.handleChange}
onBlur={props.formik.handleBlur}
value={props.formik.values.email}
error={props.formik.touched.email && Boolean(props.formik.errors.email)}
helperText={props.formik.touched.email && props.formik.errors.email}
InputLabelProps={{
shrink: true,
}}
/>
</Grid>
<Grid item xs={12} >
<MuiPhoneNumber
variant="outlined"
fullWidth
size="small"
name="phone"
label="Phone"
defaultCountry={"us"}
value={props.formik.values.phone}
error={props.formik.touched.phone && Boolean(props.formik.errors.phone)}
helperText={props.formik.touched.phone && props.formik.errors.phone}
onChange={e => props.formik.setFieldValue("phone", e)}
InputLabelProps={{
shrink: true,
}}
/>
</Grid>
<Grid sm={12}>
<TextField label="Start Date"
variant="outlined"
size="small"
name="startDate"
id="startDate"
type="date"
style={{marginTop: '17px', marginLeft: '12px'}}
defaultValue={props.formik.values.startDate}
onChange={props.formik.handleChange}
onBlur={props.formik.handleBlur}
value={props.formik.values.startDate}
error={props.formik.touched.startDate && Boolean(props.formik.errors.startDate)}
helperText={props.formik.touched.startDate && props.formik.errors.startDate}
InputLabelProps={{
shrink: true,
}}
/>
</Grid>
<Grid item xs={12} style={{marginTop: '17px'}}>
<FormControl component="fieldset" className={styles.formControl}>
<FormLabel component="legend" >Pick a color</FormLabel>
<RadioGroup row aria-label="position"
name="color"
onChange={props.formik.handleChange}
onBlur={props.formik.handleBlur}
value={props.formik.values.color}
error={props.formik.touched.color && Boolean(props.formik.errors.color)}
helperText={props.formik.touched.color && props.formik.errors.color}
>
<FormControlLabel
value="Red"
control={<Radio color="primary" />}
label="Red"
labelPlacement="start"
/>
<FormControlLabel
value="Green"
control={<Radio color="primary" />}
label="Green"
labelPlacement="start"
/>
<FormControlLabel
value="Blue"
control={<Radio color="primary" />}
label="Blue"
labelPlacement="start"
/>
</RadioGroup>
</FormControl>
{(props.formik.touched.color && Boolean(props.formik.errors.color))?
<Alert severity="error">Please choose a color</Alert>
:
''
}
</Grid>
<Grid item xs={12} style={{marginTop: '17px'}}>
<FormControl component="fieldset" className={styles.formControl}>
<FormLabel component="legend" >Do you have a Fax Number?</FormLabel>
<RadioGroup row aria-label="position"
name="hasFaxNumber"
onChange={props.formik.handleChange}
onBlur={props.formik.handleBlur}
value={props.formik.values.hasFaxNumber}
error={props.formik.touched.hasFaxNumber && Boolean(props.formik.errors.hasFaxNumber)}
helperText={props.formik.touched.hasFaxNumber && props.formik.errors.hasFaxNumber}
>
<FormControlLabel
value="Yes"
control={<Radio color="primary" />}
label="Yes"
labelPlacement="start"
/>
<FormControlLabel
value="No"
control={<Radio color="primary" />}
label="No"
labelPlacement="start"
/>
</RadioGroup>
</FormControl>
{(props.formik.touched.hasFaxNumber && Boolean(props.formik.errors.hasFaxNumber))?
<Alert severity="error">Please choose Yes or No</Alert>
:
''
}
</Grid>
<Grid item xs={12} >
{props.formik.values.hasFaxNumber === 'Yes'?
<MuiPhoneNumber
variant="outlined"
fullWidth
size="small"
name="faxNumber"
label="faxNumber"
defaultCountry={"us"}
value={props.formik.values.faxNumber}
error={props.formik.touched.faxNumber && Boolean(props.formik.errors.faxNumber)}
helperText={props.formik.touched.faxNumber && props.formik.errors.faxNumber}
onChange={e => props.formik.setFieldValue("faxNumber", e)}
InputLabelProps={{
shrink: true,
}}
/>
: ''
}
</Grid>
<Grid sm={12} style={{marginTop: '17px', marginLeft: '12px'}}>
<Button variant="contained" color="primary" onClick={props.formik.handleSubmit}>Submit</Button>
</Grid>
</Grid>
</Container>
</>
)
}
Run the App
npm start
You should see the following
Validation For Two Dates
For two dates that must be within XX number of days from each other.
This solution requires the date-fns library
Rules for validation:
The start date must be from today or 30 days in the future.
The end date must be greater than the start date and no more than 60 days later than the start date.
startDate: yup
.date()
.min(addDays(new Date(), -1), 'Start Date must start from today')
.max(addDays(new Date(), 30))
.required('Start Date is required'),
endDate: yup
.date().min(
yup.ref('startDate'),
"End date can't be before start date"
)
.when("startDate", (startDate, schema) => {
if (startDate) {
const dayAfter = addDays(new Date(startDate), 60);
return schema.max(dayAfter, 'End date can only be open for 60 days');
}
return schema;
})
.required('End Date is required'),
Programmatically Change the Input
In some cases, you may need to autofill certain fields based on a drop down or autocomplete for instance. Most recent use case was retrieving the make and model of a vehicle based on a vin number that has already been recorded in a table. If the the vin number does not exist allow the user the manually enter the make and model fields that have validation rules defined with Formik and Yup.
Using the setFieldValue() property in formik, we can update the value which satisfies the field’s validation rules.
formik.setFieldValue("FIELD_NAME", value)
A more complete example
const vehicleValidationSchema = yup.object({
vehicleMake: yup
.string('Enter Vehicle Make')
.min(2, 'Vehicle Make should be of minimum 2 characters')
.max(40, 'Vehicle Make should not exceed 40 characters')
.required('Vehicle Make is required'),
vehicleModel: yup
.string('Enter Vehicle Model')
.min(2, 'Vehicle Model should be of minimum 2 characters')
.max(40, 'Vehicle Model should not exceed 40 characters')
.required('Vehicle Model is required'),
});
//...
const formikVehicleInfo= useFormik({
initialValues: {
vehicleMake: '',
vehicleModel: ''
},
validationSchema: aircraftValidationSchema,
onSubmit: values => {
handleNext();
},
});
//...
const handleUpdateMakeModel= (makeValue, modeValue) => {
formikVehicleInfo.setFieldValue("vehicleMake", makeValue)
formikVehicleInfo.setFieldValue("vehicleModel", modeValue)
};
You must be logged in to post a comment.