mui-x icon indicating copy to clipboard operation
mui-x copied to clipboard

[pickers] Create new doc page "Integration into forms"

Open flaviendelangle opened this issue 3 years ago • 12 comments

Our old pickers documentation had this page which was never reproduced on the lab but some of our codebase comments contains links to it.

The goal of the page is to describe how to use our pickers with the most common form packages (Formik / React Final Form / React Hook Form / other ?)

flaviendelangle avatar Mar 31 '22 08:03 flaviendelangle

react-hook-form?

I can help with providing examples of a DatePicker controlled by a RHF Controller, if that helps.

Cheers, David

ddolcimascolo avatar Apr 04 '22 17:04 ddolcimascolo

Hi,

It would be great if you could give me a few example on this issue that I will adapt into this new page when writing it.

Thanks for your help !

flaviendelangle avatar Apr 05 '22 07:04 flaviendelangle

Look into and provide an example with FormData API (and/or React 19 form actions). Range picker deserves a separate attention.

LukasTy avatar May 28 '24 09:05 LukasTy

@flaviendelangle are you happy for me to work on some examples here? Looks like this has been on the cards for some time.

I'm looking at the page you linked, but it doesn't seem related (anymore?) to using the date picker within form packages. Am I missing any relevant context?

danspratling avatar Feb 26 '25 20:02 danspratling

Thank you for being interested. 🙏 Sure, feel free to explore adding form library integration examples. 👍

I'm looking at the page you linked, but it doesn't seem related (anymore?) to using the date picker within form packages. Am I missing any relevant context?

I have found a saved Wayback Machine snapshot of the linked page for reference: https://web.archive.org/web/20220331034344/https://next.material-ui-pickers.dev/guides/forms

LukasTy avatar Feb 27 '25 07:02 LukasTy

Hey @LukasTy

I've put together a getting-started doc for Formik. I'm happy to do a similar format doc for RFF and RHF too. Let me know if you have any feedback - I've tried to keep it as minimal as possible so it's not overwhelming for readers.

  • [x] Formik
  • [ ] React Final Form
  • [ ] React Hook Form

Formik

Getting started

First you’ll want to install formik

npm install formik --save

If you haven’t already, make sure to also [install MUI X](https://mui.com/x/react-date-pickers/getting-started/#installation)

npm install @mui/x-date-pickers dayjs @mui/material @emotion/react @emotion/styled

Next you’ll want to follow the datepicker install [instructions for MUI X](https://mui.com/x/react-date-pickers/getting-started/#installation) until you have a functioning datepicker

import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';

export default function FirstComponent() {
  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <DatePicker />
    </LocalizationProvider>
  );
}

Now we can expand this to work with formik

First we want to add the useFormik hook that allows us to control our form

// other imports
import { useFormik } from 'formik'

export default function App() {

  const formik = useFormik({
    initialValues: {
      // This should be set to a valid default for your date adapter
      date: null as Dayjs | null,
    },
    // This allows us to see our form values. We'd want to update this later to process our submission
    onSubmit: values => {
      alert(JSON.stringify(values, null, 2))
    },
  })
  
  return (...)
}

Then we’ll use formik in our form to manage our data

// imports
import Button from '@mui/material/Button' // For our form submit button

export default function App() {
 const formik = useFormik({
   //no changes here
 })
 
 return (
   <LocalizationProvider dateAdapter={AdapterDayjs}>
      {/* Ensure we can see our form value changes on submission */}
      <form onSubmit={formik.handleSubmit}>
        <DatePicker
          // link our value to formik
          value={formik.values.date}
          // And update it when our value changes
          onChange={(date: Dayjs) => formik.setFieldValue('date', date)}
        />
        <Button color='primary' variant='contained' fullWidth type='submit'>
          Submit
        </Button>
      </form>
    </LocalizationProvider>
  )
}

And that’s all we need for a working form! 🎉

Enhancing with validation

It’s important to validate your forms and guide users, so you probably want to add some validation

First we’ll need to add some state management for our error into our useFormik hook

export default function App() {
  const formik = useFormik({
    initialValues: {
      date: null as Dayjs | null,
    },
    // add our validate rule to check if the date is in the future
    validate: values => {
      const errors: Record<string, string> = {}

      if (values.date && values.date.isAfter(new Date())) {
        errors.date = 'Date must be in the past'
      }

      return errors
    },
    onSubmit: values => {
      alert(JSON.stringify(values, null, 2))
    },
  })

  return (
    // same as before
  )
}

Then we can link our state management to our DatePicker to ensure when something doesn’t go as planned, the user is notified.

// imports

export default function App() {
  // our error handling and useFormik hook
  
  return (
   <LocalizationProvider dateAdapter={AdapterDayjs}>
      <form onSubmit={formik.handleSubmit}>
        <DatePicker
          value={formik.values.date}
          onChange={(date: Dayjs) => formik.setFieldValue('date', date)}
          // Restrict the frontend so users can't select future dates
          disableFuture
          // and display an error if they manage to input a future date anyway
          slotProps={{
            textField: {
              helperText: formik.errors.date,
            },
          }}
        />
        <Button color='primary' variant='contained' fullWidth type='submit'>
          Submit
        </Button>
      </form>
    </LocalizationProvider>
  )
}

And that’s it! You should now have a form that allows date selection, with validation, built using formik.

Final Code

import { useFormik } from 'formik'
import Button from '@mui/material/Button'
import { Dayjs } from 'dayjs'
import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'

export default function App() {
  const formik = useFormik({
    initialValues: {
      date: null as Dayjs | null,
    },
    validate: values => {
      const errors: Record<string, string> = {}

      if (values.date && values.date.isAfter(new Date())) {
        errors.date = 'Date must be in the past'
      }

      return errors
    },
    onSubmit: values => {
      alert(JSON.stringify(values, null, 2))
    },
  })

  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <form onSubmit={formik.handleSubmit}>
        <DatePicker
          value={formik.values.date}
          onChange={(date: Dayjs) => formik.setFieldValue('date', date)}
          disableFuture
          slotProps={{
            textField: {
              helperText: formik.errors.date,
            },
          }}
        />
        <Button color='primary' variant='contained' fullWidth type='submit'>
          Submit
        </Button>
      </form>
    </LocalizationProvider>
  )
}

danspratling avatar Mar 03 '25 13:03 danspratling

Here's the react-hook-form version

As I was working on this one I realised that I should probably be using the form libraries built-in validation so I'll come back and fix the formik one via edits

Edit: The formik example is now updated to use the formik error management

  • [x] Formik
  • [ ] React Final Form
  • [x] React Hook Form

React Hook Form

Initial setup

First you’ll want to install react-hook-form

npm install react-hook-form

If you haven’t already, make sure to also [install MUI X](https://mui.com/x/react-date-pickers/getting-started/#installation)

npm install @mui/x-date-pickers dayjs @mui/material @emotion/react @emotion/styled

Next you’ll want to follow the datepicker install [instructions for MUI X](https://mui.com/x/react-date-pickers/getting-started/#installation) until you have a functioning datepicker

import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';

export default function FirstComponent() {
  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <DatePicker />
    </LocalizationProvider>
  );
}

Now we can expand this to work with react-hook-form

First we want to add the useForm hook that allows us to control our form, and our onSubmit function that tells the form what to do when a submit event occurs.

// other imports
import { useForm, Controller, SubmitHandler } from 'react-hook-form'

// define our form input types
type Inputs = {
  date: Dayjs | null
}

export default function App() {
  const { control, handleSubmit } = useForm<Inputs>()
  const onSubmit: SubmitHandler<Inputs> = data => alert(data)
  
  return (...)
}

If you’re familiar with react-hook-form you may be used to using the register method, which you’ll notice we’re not using here. To integrate with external UI libraries, react-hook-form instead exposes a control method (link) which allows us to pass the data used by react-hook-form to its children.

Now we can add our controlled DatePicker

// imports
import Button from '@mui/material/Button' // For our form submit button

export default function App() {
 // Everything here stays the same
 
 return (
   <LocalizationProvider dateAdapter={AdapterDayjs}>
      {/* We add a handleSubmit function */}
      <form onSubmit={handleSubmit(onSubmit)}>
        {/* A controller component wraps our DatePicker to pass it the appropriate props */}
        <Controller
          name='date'
          control={control}
          render={({ field: { onChange, value } }) => (
            // And we pass value and onChange to our DatePicker, which are managed by react-hook-form
            <DatePicker value={value} onChange={onChange} />
          )}
        />
        <Button color='primary' variant='contained' fullWidth type='submit'>
          Submit
        </Button>
      </form>
    </LocalizationProvider>
  )
}

And now you can submit your form and see the date being sent! 🎉

Enhancing with validation

It’s important to validate your forms and guide users, so you probably want to add some validation

We can use react-hook-form’s validation to assist us with our state management, but we’ll need to make a few changes to use it.

// imports & 'Inputs' type declaration

export default function App() {
  const {
    control,
    handleSubmit,
    // extract our errors from the useForm hook
    formState: { errors },
  } = useForm<Inputs>()
  const onSubmit: SubmitHandler<Inputs> = data => alert(JSON.stringify(data))

  return (...)
}

Now we can access any errors, we need to capture that something went wrong


export default function App() {
  //

  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <form onSubmit={handleSubmit(onSubmit)}>
        <Controller
          name='date'
          control={control}
          {/* Add a rule that checks if the date is in the past, and send an error message of 'disableFuture' if not */}
          rules={{ validate: value => (value && value.isBefore() ? undefined : 'disableFuture') }}
          render={({ field: { onChange, value } }) => (
            <DatePicker 
              value={value} 
              onChange={onChange}
              // Add 'disableFuture' to our frontend validation to prevent users selecting incorrect dates
              disableFuture 
              // And add in a message that tells users where they went wrong if an error appears
              slotProps={{
                textField: {
                  helperText: errors.date?.type === 'validate' && errors.date?.message === 'disableFuture' && 'Selected date must be in the past'
                },
              }} 
            />
          )}
        />
        <Button color='primary' variant='contained' fullWidth type='submit'>
          Submit
        </Button>
      </form>
    </LocalizationProvider>
  )
}

And that’s it! You should now have a form that allows date selection, with validation, built using react-hook-form.

Final code

import { useForm, Controller, SubmitHandler } from 'react-hook-form'
import Button from '@mui/material/Button'
import { Dayjs } from 'dayjs'
import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'

type Inputs = {
  date: Dayjs | null
}

export default function App() {
  const {
    control,
    handleSubmit,
    formState: { errors },
  } = useForm<Inputs>()
  const onSubmit: SubmitHandler<Inputs> = data => alert(JSON.stringify(data))

  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <form onSubmit={handleSubmit(onSubmit)}>
        <Controller
          name='date'
          control={control}
          rules={{ validate: value => (value && value.isBefore() ? undefined : 'disableFuture') }}
          render={({ field: { onChange, value } }) => (
            <DatePicker 
              value={value} 
              onChange={onChange} 
              disableFuture 
              slotProps={{
                textField: {
                  helperText: errors.date?.type === 'validate' && errors.date?.message === 'disableFuture' && 'Selected date must be in the past'
                },
              }} 
            />
          )}
        />
        <Button color='primary' variant='contained' fullWidth type='submit'>
          Submit
        </Button>
      </form>
    </LocalizationProvider>
  )
}

danspratling avatar Mar 04 '25 11:03 danspratling

And here's one for React Final Form

  • [x] Formik
  • [x] React Final Form
  • [x] React Hook Form

React Final form

Initial setup

First you’ll want to install React Final Form

npm install --save final-form react-final-form

If you haven’t already, make sure to also [install MUI X](https://mui.com/x/react-date-pickers/getting-started/#installation)

npm install @mui/x-date-pickers dayjs @mui/material @emotion/react @emotion/styled

Next you’ll want to follow the datepicker install [instructions for MUI X](https://mui.com/x/react-date-pickers/getting-started/#installation) until you have a functioning datepicker

import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';

export default function FirstComponent() {
  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <DatePicker />
    </LocalizationProvider>
  );
}

Now we can expand this to work with React Final Form

First we want to add our onSubmit function so that we can see our results

// other imports
import { Form } from 'react-final-form'

export default function App() {
  const onSubmit = (values: { date: Dayjs }) => alert(JSON.stringify(values, null, 2))
  
  return (...)
}

Then we can add the Final Form wrapping component around our DatePicker

//imports

export default function App() {
  const onSubmit = (values: { date: Dayjs }) => alert(JSON.stringify(values, null, 2))
  
  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      {/* We wrap our form with a Final Form component to handle our state */}
      <Form
        onSubmit={onSubmit}
        initialValues={{ date: null }}
        render={({ handleSubmit, form, values }) => (
          // This manages our submit state which we pass to our form element
          <form onSubmit={handleSubmit}>
            // It also manages our state which we pass to our DatePicker component
            <DatePicker
              value={values.date}
              onChange={(date: Dayjs) => form.change('date', date)}
            />
            <Button color='primary' variant='contained' fullWidth type='submit'>
              Submit
            </Button>
          </form>
        )}
      />
    </LocalizationProvider>
  )
}

And that’s it! We now have a working form using React Final Form

Enhancing with validation

It’s important to validate your forms and guide users, so you probably want to add some validation

First we need to additionally import the Field component from React Final form

import { Form, Field } from 'react-final-form'

Then we need to add validation handling to the form

// imports

export default function App() {
  const onSubmit = (values: { date: Dayjs }) => alert(JSON.stringify(values, null, 2))

  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <Form
        onSubmit={onSubmit}
        // add an additional prop to validate our form values
        validate={values => {
          const errors: Record<string, string> = {}

          if (values.date && values.date.isAfter(new Date())) {
            errors.date = 'Date must be in the past'
          }

          return errors
        }}
        render={
          //remains the same for now
        }
      />
    </LocalizationProvider>
  )
}

Now that we can handle errors, we need to update our field to actually manage the validation

// imports

export default function App() {
  const onSubmit = (values: { date: Dayjs }) => alert(JSON.stringify(values, null, 2))

  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <Form
        onSubmit={onSubmit}
        validate={values => {
          const errors: Record<string, string> = {}

          if (values.date && values.date.isAfter(new Date())) {
            errors.date = 'Date must be in the past'
          }

          return errors
        }}
        render={({ handleSubmit }) => (
          <form onSubmit={handleSubmit}>
            {/* We add a wrapper component for our input to access error states */}
            <Field name='date' initialValue={null}>
              {({ input, meta }) => (
                <DatePicker
                  // This also handles our field state, so we switch to use that instead
                  {...input}
                  // Handle empty values, as Field returns "" initially while MUI X expects null
                  value={input.value ? input.value : null}
                  // Prevent users from selecting future values
                  disableFuture
                  // Display an error to the user, if one exists
                  slotProps={{
                    textField: {
                      helperText: meta.error ? meta.error : undefined,
                    },
                  }}
                />
              )}
            </Field>

            <Button color='primary' variant='contained' fullWidth type='submit'>
              Submit
            </Button>
          </form>
        )}
      />
    </LocalizationProvider>
  )
}

And that’s it! You should now have a form that allows date selection, with validation, built using React Final Form.

Final Code

import { Form, Field } from 'react-final-form'
import Button from '@mui/material/Button'
import { Dayjs } from 'dayjs'
import { DatePicker, LocalizationProvider } from '@mui/x-date-pickers'
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'

export default function App() {
  const onSubmit = (values: { date: Dayjs }) => alert(JSON.stringify(values, null, 2))

  return (
    <LocalizationProvider dateAdapter={AdapterDayjs}>
      <Form
        onSubmit={onSubmit}
        validate={values => {
          const errors: Record<string, string> = {}

          if (values.date && values.date.isAfter(new Date())) {
            errors.date = 'Date must be in the past'
          }

          return errors
        }}
        render={({ handleSubmit }) => (
          <form onSubmit={handleSubmit}>
            <Field name='date' initialValue={null}>
              {({ input, meta }) => (
                <DatePicker
                  {...input}
                  value={input.value ? input.value : null}
                  disableFuture
                  slotProps={{
                    textField: {
                      helperText: meta.error ? meta.error : undefined,
                    },
                  }}
                />
              )}
            </Field>

            <Button color='primary' variant='contained' fullWidth type='submit'>
              Submit
            </Button>
          </form>
        )}
      />
    </LocalizationProvider>
  )
}

danspratling avatar Mar 05 '25 11:03 danspratling

I've also been looking into other potential options but it seems that most are EOL, so with these 3 covered MUI should have it's bases covered.

Other options looked at

  • Unform - Archived and not updated since 2023
  • Redux Form - Unmaintained, replaced by React Final Form
  • react-json-schema-form - candidate for adding
  • Tanstack Form - potential candidate, but it's only just released

danspratling avatar Mar 05 '25 11:03 danspratling

Thanks for the amazing work 🙏 We'll try to prioritize a PR to review and apply your content on a new doc page.

flaviendelangle avatar Mar 12 '25 07:03 flaviendelangle

If understand the issue well, those libraries https://mui.com/material-ui/discover-more/related-projects/#form should be up to date and support the picker components.

However, showing an open API way to do it, like https://ui.shadcn.com/docs/components/form#examples has for each component makes sense 👍

oliviertassinari avatar Apr 21 '25 14:04 oliviertassinari

If understand the issue well, those libraries mui.com/material-ui/discover-more/related-projects#form should be up to date and support the picker components.

react-hook-form-mui unfortunately is not up to date and does not yet support v8 of the picker.

JanPretzel avatar May 27 '25 10:05 JanPretzel