Material-UI pickers
v4.0.0-alpha.12

Integration into forms#

Pickers are quite complex controls, where date can be submitted from different places, so we can't provide events as arguments in an onChange callback. So you need to set up form field manually, based on the onChange param.

Also we are providing prop onError that is ready-to-use for validation. This callback fires once the selected value by user is becomes invalid based on the passed validation props. It returns you a reason why error was occured so it is possible to render custom error message.

Validation props#

There are different validation props for each component. The reason prop returned by onError will basically return you a name of prop that match an error (e.g. minDate or shouldDisableDate)

For reference, here is the list of validation props for DatePicker in the order of applying:

  • shouldDisableDate
  • disableFuture
  • disablePast
  • minDate
  • maxDate

Formik#

Here is an example how to use onError callback in integration with formik

/* eslint-disable no-console */
// @ts-nocheck
import * as React from "react";
import Grid from "@material-ui/core/Grid";
import TextField from "@material-ui/core/TextField";
import { Formik, Form, Field, FieldProps } from "formik";
import { format, isWeekend, isWednesday } from "date-fns";
import { DatePicker, DatePickerProps } from "@material-ui/pickers";

interface DatePickerFieldProps extends FieldProps, DatePickerProps {
  getShouldDisableDateError: (date: Date) => string;
}

function DatePickerField({
  form,
  field: { value, name },
  maxDate = new Date("2099-12-31"),
  minDate = new Date("1900-01-01"),
  getShouldDisableDateError,
  ...other
}: DatePickerFieldProps) {
  const currentError = form.errors[name];
  const toShowError = Boolean(currentError && form.touched[name]);

  return (
    <DatePicker
      clearable
      minDate={minDate}
      maxDate={maxDate}
      value={value}
      onError={(reason, value) => {
        switch (reason) {
          case "invalidDate":
            form.setFieldError(name, "Invalid date format");
            break;

          case "disablePast":
            form.setFieldError(name, "Values in the past are not allowed");
            break;

          case "maxDate":
            form.setFieldError(name, `Date should not be after ${format(maxDate, "P")}`);
            break;

          case "minDate":
            form.setFieldError(name, `Date should not be before ${format(minDate, "P")}`);
            break;

          case "shouldDisableDate":
            // shouldDisableDate returned true, render custom message according to the `shouldDisableDate` logic
            form.setFieldError(name, getShouldDisableDateError(value));
            break;

          default:
            form.setErrors({
              ...form.errors,
              [name]: undefined,
            });
        }
      }}
      // Make sure that your 3d param is set to `false` on order to not clear errors
      onChange={(date) => form.setFieldValue(name, date, false)}
      renderInput={(props) => (
        <TextField
          {...props}
          name={name}
          error={toShowError}
          helperText={toShowError ? currentError ?? props.helperText : undefined}
          // Make sure that your 3d param is set to `false` on order to not clear errors
          onBlur={() => form.setFieldTouched(name, true, false)}
        />
      )}
      {...other}
    />
  );
}

function validateDatePickerValue(date: Date) {
  if (isWeekend(date)) {
    return "Weekends are not allowed";
  }

  if (isWednesday(date)) {
    return "Wednesdays are not allowed";
  }

  return null;
}

export default function FormikExample() {
  return (
    <Formik onSubmit={console.log} initialValues={{ date: new Date() }}>
      {({ values, errors }) => (
        <Form>
          <Grid container>
            <Grid item container justifyContent="center" xs={12}>
              <Field
                name="date"
                disablePast
                component={DatePickerField}
                shouldDisableDate={(date: Date) => validateDatePickerValue(date) !== null}
                getShouldDisableDateError={validateDatePickerValue}
              />
            </Grid>
            <Grid item xs={12} sm={12} style={{ margin: "24px" }}>
              <pre>
                <code>{JSON.stringify({ errors, values }, null, 2)}</code>
              </pre>
            </Grid>
          </Grid>
        </Form>
      )}
    </Formik>
  );
}
{
  "errors": {},
  "values": {
    "date": "2021-12-08T02:33:37.205Z"
  }
}

Formik with validation schema#

This example shows how to use formik and custom validation schema. When using this approach please make sure that your validation schema logic covers all the validation props that are passed to the DatePicker.

/* eslint-disable no-console */
// @ts-nocheck
import React from "react";
import Grid from "@material-ui/core/Grid";
import TextField from "@material-ui/core/TextField";
import { date, object } from "yup";
import { Formik, Form, Field, FieldProps } from "formik";
import { DatePicker, BaseDatePickerProps } from "@material-ui/pickers";

interface DatePickerFieldProps extends FieldProps, BaseDatePickerProps {
  getShouldDisableDateError: (date: Date) => string;
}

function DatePickerField(props: DatePickerFieldProps) {
  const {
    field,
    form,
    getShouldDisableDateError,
    maxDate = new Date("2099-12-31"),
    minDate = new Date("1900-01-01"),
    ...other
  } = props;
  const currentError = form.errors[field.name];

  return (
    <DatePicker
      clearable
      minDate={minDate}
      maxDate={maxDate}
      value={field.value}
      // Make sure that your 3d param is set to `true` in order to run validation
      onChange={(newValue) => form.setFieldValue(field.name, newValue, true)}
      renderInput={(inputProps) => (
        <TextField
          name={field.name}
          {...inputProps}
          error={Boolean(currentError)}
          helperText={currentError ?? inputProps.helperText}
          // Make sure that your 3d param is set to `true` in order to run validation
          onBlur={() => form.setFieldTouched(field.name, true, true)}
        />
      )}
      {...other}
    />
  );
}

const schema = object({
  date: date().required().min(new Date()).max(new Date("2100-10-10")),
});

export default function FormikValidationSchemaExample() {
  return (
    <Formik validationSchema={schema} onSubmit={console.log} initialValues={{ date: new Date() }}>
      {({ values, errors }) => (
        <Form>
          <Grid container>
            <Grid item container justifyContent="center" xs={12}>
              <Field name="date" disablePast component={DatePickerField} />
            </Grid>
            <Grid item xs={12} sm={12} style={{ margin: "24px" }}>
              <pre>
                <code>{JSON.stringify({ errors, values }, null, 2)}</code>
              </pre>
            </Grid>
          </Grid>
        </Form>
      )}
    </Formik>
  );
}
{
  "errors": {},
  "values": {
    "date": "2021-12-08T02:33:37.226Z"
  }
}