Integrating Formik with React Material UI and Typescript
This guide covers one approach for integrating Formik with Material UI in a Typescript project. Formik is a popular form state management library for React.
A codesandbox related to this post is at:
https://codesandbox.io/s/formik-typescript-material-ui-gn692
When integrating Formik with Material UI there are a few approaches that you can take:
- Defining a custom component that takes advantage of the
useField()
hook - Using Formik’s
Field
orFastField
components and passing your Material UI component via itsas
,children
, orcomponent
props
This guide details the latter approach for common/basic form use-cases because it works very well with the Material UI TextField
.
The Formik Component
To get started, all forms that are managed by Formik must be wrapped in a <Formik>
component. This component receives several props including initialValues
and onSubmit
.
For validation, you can pass a function to the validate
prop that receives the form values
. Alternately you can use the yup object schema validation library. The validationSchema
prop accepts a yup schema or a function that returns a yup schema.
The <Formik>
component uses the render-props pattern to make formikProps
available to child components. These include: values
, touched
, errors
, handleChange
, handleBlur
, isValid
, and isSubmitting
.
This component is also a context provider that provides form context to its children. Formik includes Field
and FastField
components that tap into the context. The context can also be accessed by any child component that takes advantage of the useFormikContext()
hook.
Refer to the docs for the full suite of options regarding the Formik
component.
Start by defining an interface that represents your form values:
interface FormValues {
name: string;
description: string;
}
The Formik
component can then be used as follows:
<Formik
initialValues={{
name: "",
description: ""
}}
validationSchema={validationSchema} // assuming you have defined one
onSubmit={(
values: FormValues,
formikHelpers: FormikHelpers<FormValues>
) => {
alert(JSON.stringify(values, null, 2));
formikHelpers.setSubmitting(false);
}}
>
{(formikProps: FormikProps<FormValues>) => (
...
// formikProps includes: values, touched, errors, handleChange, handleBlur, isValid, isSubmitting, etc
// tip: isSubmitting is useful for disabling the submit button during form submission
...
})
</Formik>
The initialValues
should be of the type FormValues
. On submit, the values received will also be of this type.
Note that the onSubmit()
function can be async and return a promise. If the function returns a promise, Formik will automatically update the form’s submitting
flag to false
when the promise resolves/rejects.
The Form Component
All web forms should be wrapped with a <form>
tag.
Formik provides a convenient <Form>
component that automatically hooks into Formik’s handleSubmit()
and handleReset()
functions. It is essentially a wrapper for the following:
<form onReset={formikProps.handleReset} onSubmit={formikProps.handleSubmit} {...props}>...</form>
The <Form>
component is commonly placed as the first child of the <Formik>
component.
Material UI Fields
With the opening Formik
and Form
components out of the way, we can now specify our form’s fields.
To integrate with Material UI, one approach is to build your own Formik-compatible components with the useField()
hook:
const [field, meta, helpers] = useField('example')
The field
property holds name
, value
, and onChange
and is designed such that these properties can be passed straight to an input
tag.
The meta
property holds metadata about the field, including error
and touched
. These are useful for displaying validation errors, which you may only want to do after your field has been touched
by the user.
The helpers
are helper functions that enable you to integrate with the formik state: setValue()
, setTouched()
, and setError()
.
I think a more straightforward approach, especially for basic/common forms comprised of Material UI TextField
components, is to leverage Formik’s <Field>
and <FastField>
components. These components automatically tap into the context provided by a parent <Formik>
component. You supply the name
prop and the components hook up the name
, value
, onChange
, and onBlur
to an underlying input component.
With Field and FastField you can specify a specific component to render using one of the as
, children
, or component
props. Refer to the docs for the differences between these options.
This guide will focus on using the component
prop. This prop accepts either a React component or the name of an HTML element to render, and it passes through all additional props. This prop-passing behaviour makes it ideal for use with a Material UI component because you can also specify any props specific to Material UI.
Material UI includes a helpful <TextField>
component that renders a complete form control including a label, an input, and help text. The component and help text is presented with error styles (red outline by default) if the error
prop is set to true
.
I suggest wrapping the Material UI TextField
in your own Formik-friendly component as follows:
import React from 'react'
import { FieldProps, getIn } from 'formik'
import { TextFieldProps, TextField } from '@material-ui/core'
export const AppTextField: React.FC<FieldProps & TextFieldProps> = props => {
const isTouched = getIn(props.form.touched, props.field.name)
const errorMessage = getIn(props.form.errors, props.field.name)
const { error, helperText, field, form, ...rest } = props
return (
<TextField
error={error ?? Boolean(isTouched && errorMessage)}
helperText={helperText ?? ((isTouched && errorMessage) ? errorMessage : undefined)}
{...rest} // includes any Material-UI specific props
{...field} // includes all props contributed by the Formik Field/FastField
/>
)
}
The above implementation allows you to override the error
and helperText
props, otherwise it defaults to logic that works well with Formik.
Now, inside your <Form>
, you can specify fields as follows:
<Field name="name" component={AppTextField} />
<Field name="description" component={AppTextField} />
You can also include any props specific to customizing the Material UI TextField, e.g.:
<Field name="name" label="Name" variant="outlined" size="small" component={AppTextField} />
Check out the demo on codesandbox for a complete implementation (including validation with yup) to see how everything works including the display of validation errors:
https://codesandbox.io/s/formik-typescript-material-ui-gn692