[pickers] Create new doc page "Integration into forms"
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 ?)
react-hook-form?
I can help with providing examples of a DatePicker controlled by a RHF Controller, if that helps.
Cheers, David
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 !
Look into and provide an example with FormData API (and/or React 19 form actions). Range picker deserves a separate attention.
@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?
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
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>
)
}
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>
)
}
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>
)
}
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
Thanks for the amazing work 🙏 We'll try to prioritize a PR to review and apply your content on a new doc page.
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 👍
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.