React Material Date Picker Validation with Formik

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 Files

Simple 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 

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>

     )  
}