React Material Date Picker Validation with Formik

Table of Contents
Overview
Working examples of Material Date Pickers for start and end dates. Both dates are required and the end date must have a date on or after the start date.
Real world scenarios – Events and reservations.
Source Files for Both Examples
Get Source FilesSimple Solution
First solution is simply disable certain dates using the Material Date Picker properties. Also, not shown here is using a Date Range Picker. The following examples use two Date Pickers to get a range.
Libraries Needed
MUI – Material UI v5.4.3
Material UI library for React
npm install @mui/material @mui/lab @emotion/react @emotion/styled
Date-fns – v2.28.0
LocalizationProvider requires a manual install of Date-FNS date-io adapter to use AdapterDateFns.
npm install date-fns
Date-fns – v2.28.0
LocalizationProvider requires a manual install of Date-FNS date-io adapter to use AdapterDateFns.
npm install date-fns
MUI Date Pickers
Using AdapterDateFns, LocalizationProvider, DateTimePicker the date picker is rendered with a default date. You need the date-io adapter Date-FNS installed in order to use the MUI v5 Date Pickers. Date Pickers must be wrapped with the <LocalizationProvider /> component from MUI,
import AdapterDateFns from '@mui/lab/AdapterDateFns';
import LocalizationProvider from '@mui/lab/LocalizationProvider';
import DateTimePicker from '@mui/lab/DateTimePicker';
...
const now = new Date();
const today = now.setHours(9,0,0,0);
const tomorrow = new Date(now.setDate(now.getDate() + 1));
const nextWeek = new Date(now.setDate(now.getDate() + 7)).setHours(18,0,0,0);
const [startDateValue, setStartDateValue] = React.useState(tomorrow);
...
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DateTimePicker
label="Start Date"
value={startDateValue}
onChange={(newValue) => {
setStartDateValue(newValue);
}}
renderInput={(params) => <TextField {...params} sx={{ width: 240 }}/>}
/>
</LocalizationProvider>
How to Disable Past Dates
Use the disable dates property of the MUI date pickers. In the example below you have a start and end date. The start date automatically fills the end date and disables all days before the start dates.

import React, {useState, useEffect} from 'react';
import AdapterDateFns from '@mui/lab/AdapterDateFns';
import LocalizationProvider from '@mui/lab/LocalizationProvider';
import DesktopDatePicker from '@mui/lab/DesktopDatePicker';
import Button from '@mui/material/Button';
import { Grid, TextField } from '@mui/material';
//...
const [startDate, setStartDate] = useState(null);
const [endDate, setEndDate] = useState(null);
const handleStartDate = (value) => {
setStartDate(value)
setEndDate(value)
}
const handleEndDate = (value) => {
setEndDate(value)
}
//...
return (
<>
<Grid container spacing={2}>
<Grid item xs={12} align="center">
<h2>MUI Date Pickers</h2>
</Grid>
<Grid item xs={4} align="left"></Grid>
<Grid item xs={2} align="left">
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DesktopDatePicker
disablePast
name="startDate"
value={startDate}
onChange={(newValue) => {
handleStartDate(newValue);
}}
label="Start Date"
inputFormat="MM/dd/yyyy"
renderInput={(params) => <TextField
size="small" {...params} sx={{ width: '100%' }}/>}
/>
</LocalizationProvider>
</Grid>
<Grid item xs={2} align="left">
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DesktopDatePicker
label="End Date"
inputFormat="MM/dd/yyyy"
value={endDate}
fullWidth
minDate={endDate}
onChange={(newValue) => {
handleEndDate(newValue);
}}
renderInput={(params) => <TextField size="small" {...params} sx={{ width: '100%' }}/>}
/>
</LocalizationProvider>
</Grid>
<Grid item xs={4} align="left"></Grid>
<Grid item xs={12} align="center">
<Button variant="contained" color="primary" disabled={!startDate || !endDate}>Submit</Button>
</Grid>
</Grid>
</>
)
Bonus – Exclude Weekends
The shouldDisableDate property take a function to disable dates.
Example shouldDisableDate={disableWeekends}
The function below simply returns a true or false the date to exclude. In the example below the date.getDay() === 0 or 6. 0 is Sunday, 1 is Monday and so on.
The same can be done
function disableWeekends(date) {
return date.getDay() === 0 || date.getDay() === 6;
}
....
<LocalizationProvider dateAdapter={AdapterDateFns}>
<DesktopDatePicker
label="Departure Date"
inputFormat="MM/dd/yyyy"
value={startDate}
fullWidth
disabled={props.dropGo}
shouldDisableDate={disableWeekends}
helperText={'Invalid Date'}
nChange={(newValue) => { handleDepartureDate(newValue) }}
renderInput={(params) =>
<TextField size="small" {...params} sx={{ width: '100%' }}/>}
/>
</LocalizationProvider>
Validation with Formik and Yup
Libraries Needed
MUI – Material UI v5.4.3
MUI Material UI library for React
npm install @mui/material @mui/lab @emotion/react @emotion/styled
Date-FNS v2.28.0
LocalizationProvider requires a manual install of Date-fns date-io adapter to use AdapterDateFns.
npm install date-fns
Formik – v2.28.0
Formik-MUI v4.0.0-alpha.3
Formik-MUI-Lab v1.0.0-alpha.3
Yup v0.32.11
Formik, Formik-MUI, Formik-MUI-Lab, and Yup required to perform validation on the Date Pickers
npm install formik formik-mui formik-mui-lab yup

Yup, that’s a lot of libraries just to validate some MUI Date Pickers.
Yup Validation Schema
Using date() to define the required type.
const formValidationSchema = yup.object({
startDate: yup
.date('Start Date is Required')
.typeError("Start date is required")
.required('Start Date is required'),
endDate: yup
.date('')
.typeError("End date is required")
.when("startDate",
(started, yup) => started && yup.min(started, "End date cannot be before start date"))
.required('End Date is required'),
});
Tips for Yup Validation Schema
Use typeError() to override the error message. The default message is pretty long.
Default date field error message

Using a custom message with typeError().
.typeError("Enter a value End date")

Validation Condition for End Date to be Greater than Start Date using the when() and min() properties.
.when("startDate",
(started, yup) => started && yup.min(started, "End date cannot be before start date"))
.required('End Date is required'),
Building the Component
Libraries to Import
import React from 'react';
import { Formik, Form, Field } from 'formik';
import * as yup from 'yup';
import AdapterDateFns from '@mui/lab/AdapterDateFns';
import LocalizationProvider from '@mui/lab/LocalizationProvider';
import { DesktopDatePicker } from 'formik-mui-lab';
import Button from '@mui/material/Button';
import { Grid } from '@mui/material';
Using MUI Formik Components
Wrap the MUI Date Pickers with a <Formik /> component. Initialize the date fields labeled the same as in Yup. Use the validationSchema property to include the Yup validation schema defined above.
<Formik
initialValues={{
startDate: '',
endDate: '',
}}
validationSchema={DisplayingErrorMessagesSchema}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
setSubmitting(false);
alert(JSON.stringify(values, null, 2));
}, 500);
}}
>
Date Pickers
With the <Field/> component from Formik and pass MUI’s DatePicker, MobilePicker, DesktopDatePicker etc to the component property. DesktopDatePicker used in the example code.
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Formik
initialValues={{
startDate: '',
endDate: '',
}}
validationSchema={DisplayingErrorMessagesSchema}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
setSubmitting(false);
alert(JSON.stringify(values, null, 2));
}, 500);
}}
>
{({ errors, touched, values }) => (
<Form>
<Grid container spacing={2}>
<Grid item xs={12} align="center">
<h1>Formik Validate Date Pickers</h1>
</Grid>
<Grid item xs={3} align="left"></Grid>
<Grid item xs={3} align="right">
<Field
component={DesktopDatePicker}
disablePast
views={['year', 'month', 'day']}
name="startDate"
label="Start Date" />
</Grid>
<Grid item xs={3} align="left">
<Field
component={DesktopDatePicker}
name="endDate"
label="End Date"
views={['year', 'month', 'day']}
/>
</Grid>
<Grid item xs={3} align="left"></Grid>
<Grid item xs={12} align="center">
<Button
type="submit"
variant="contained"
color="primary"
>
Submit
</Button>
</Grid>
</Grid>
</Form>
)}
</Formik>
</LocalizationProvider>
All Together
The source code for the entire component.
import React from 'react';
import { Formik, Form, Field } from 'formik';
import * as yup from 'yup';
import AdapterDateFns from '@mui/lab/AdapterDateFns';
import LocalizationProvider from '@mui/lab/LocalizationProvider';
import { DesktopDatePicker } from 'formik-mui-lab';
import Button from '@mui/material/Button';
const DisplayingErrorMessagesSchema = yup.object().shape({
startDate: yup
.date().nullable()
.typeError('Start date is required')
.required('Start Date is required'),
endDate: yup
.date().nullable()
.when("startDate",
(startDate, yup) => startDate && yup.min(startDate, "End date cannot be before start time"))
.required('End Date is required')
.typeError('Enter a value End date')
});
export default function DateValidationWithFormik() {
return (
<div>
<h1>Formik Validate Date Pickers</h1>
<LocalizationProvider dateAdapter={AdapterDateFns}>
<Formik
initialValues={{
startDate: '',
endDate: '',
}}
validationSchema={DisplayingErrorMessagesSchema}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
setSubmitting(false);
alert(JSON.stringify(values, null, 2));
}, 500);
}}
>
{({ errors, touched, values }) => (
<Form>
<Field
component={DesktopDatePicker}
disablePast
views={['year', 'month', 'day']}
name="startDate"
label="Start Date" />
{touched.startDate && errors.startDate && <div>{errors.startDate}</div>}
<Field
component={DesktopDatePicker}
name="endDate"
label="End Date"
views={['year', 'month', 'day']}
/>
{touched.endDate && errors.endDate && <div>{errors.endDate}</div>}
<Button
type="submit"
variant="contained"
color="primary"
>
Submit
</Button>
</Form>
)}
</Formik>
</LocalizationProvider>
</div>
)
}
You must be logged in to post a comment.