Using Formik 2 with React Material Design
Formik is perhaps the leading choice of library to help implement forms in React. Version 2 was recently released and it introduces new hooks as well as improved support for checkboxes and select fields.
This post covers basic usage of Formik v2 with the TextField
, Radio
, and Checkbox
components provided by the Material UI library.
Starting with a blank Create React App project, add the appropriate dependencies:
yarn add formik
yarn add @material-ui/core
You may also wish to add the Roboto font to Material UI per the installation guide.
Start by importing the Formik
component.
import { Formik } from 'formik'
Next add the Formik
component to your app. It has two required props: initialValues
and onSubmit
.
The initialValues
prop is for specifying an object with properties that correspond to each field in your form. Each key of the object should match the name of an element in your form.
The onSubmit
prop receives a function that is called when the form is submitted. The function is passed a data
parameter containing the submitted form’s data, and an object with properties that contain a number of functions that you can use to help disable the submit button, reset the form, and more (refer to the docs). In the example below, the function implementation simply logs the data to the console.
The Formik
component accepts a function as a child. Formik provides a number of properties as a parameter to the function. The most immediately relevant properties that can be pulled out using destructuring are values
(an object that represents the current state of the form), and the functions handleChange
, handleBlur
, and handleSubmit
.
For Material, import a TextField
and a Button
component:
import TextField from '@material-ui/core/TextField'
import Button from '@material-ui/core/Button'
And incorporate them into Formik as follows:
function App() {
return (
<div>
<Formik
initialValues={{ example: '' }}
onSubmit={(data) => {
console.log(data)
}}
>{({ values, handleChange, handleBlur, handleSubmit }) => (
<form onSubmit={handleSubmit}>
<TextField name="example" onChange={handleChange} onBlur={handleBlur} value={values.example} />
<Button type="submit">Submit</Button>
</form>
)}</Formik>
</div>
)
}
To simplify the tedious process of adding values
, handleChange
, handleBlur
, and handleSubmit
you can use Formik’s helper components Form
and Field
.
The Form
component replaces the standard HTML form
tag. It is automagically passed the onSubmit
/handleSubmit
function (via internal use of the Context API) so you don’t need to add this every time.
The Field
component needs to only be passed a name
and type
prop. It automagically gets the value
, onChange
, and onBlur
.
A Field
component with type
“text” will render a default HTML5 input by default. To use Material, there’s another prop, as
, where you can pass a component that you want the field to render as. As long as the component you pass is capable of accepting value
, onChange
, and onBlur
props (as Material’s TextField
does) then you can use it. The Field
component will also pass any additional props it is given (e.g. placeholder
) to the component specified in the as
prop.
import { Formik, Form, Field } from 'formik'
function App() {
return (
<div>
<Formik
initialValues={{ example: '' }}
onSubmit={(data) => {
console.log(data)
}}
>{({ values }) => (
<Form>
<Field name="example" type="input" as={TextField} />
<Button type="submit">Submit</Button>
</Form>
)}</Formik>
</div>
)
}
The same technique works for checkboxes and radio buttons as the following example demonstrates:
import Radio from '@material-ui/core/Radio'
import Checkbox from '@material-ui/core/Checkbox'
function App() {
return (
<div>
<Formik
initialValues={{ example: '', name: '', bool: false, multi: [], one: '' }}
onSubmit={(data) => {
console.log(data)
}}
>{({ values }) => (
<Form>
<div>
<Field name="example" type="input" as={TextField} />
</div>
<div>
<Field name="name" type="input" as={TextField} />
</div>
<div>
<Field name="bool" type="checkbox" as={Checkbox} />
</div>
<div>
<Field name="multi" value="asdf" type="checkbox" as={Checkbox} />
<Field name="multi" value="fdsa" type="checkbox" as={Checkbox} />
<Field name="multi" value="qwerty" type="checkbox" as={Checkbox} />
</div>
<div>
<Field name="one" value="sun" type="radio" as={Radio} />
<Field name="one" value="moon" type="radio" as={Radio} />
</div>
<Button type="submit">Submit</Button>
</Form>
)}</Formik>
</div>
)
}
However, if we want to show labels beside our fields, we run into an issue with how React Material is implemented. It uses a FormControlLabel
component that is in turn passed the component to render via its control
prop. Check the docs at:
This doesn’t jive well with our current paradigm. It is cleanest to implement a custom field.
Formik v2 adds a very convenient hook called useField()
to facilitate creating a custom field. The hook returns an array containing a field
object that contains the value
, onChange
, etc. and a meta
object which is useful for form validation. It contains properties such as error
and touched
.
import { useField } from 'formik'
In the example below, the value
, onChange
, etc properties are added to the FormControlLabel
as props using the spread operator: {...field}
.
import FormControlLabel from '@material-ui/core/FormControlLabel'
function ExampleRadio({ label, ...props }) {
const [ field, meta ] = useField(props)
return (
<FormControlLabel {...field} control={<Radio />} label={label} />
)
}
Now the ExampleRadio
component that was implemented with the help of the useField()
hook can replace the Field
component with type
“radio” in the above examples:
<ExampleRadio name="one" value="sun" type="radio" label="sun" />
So there you have it, a basic use of Formik 2 with React Material that works for the most popular form fields.
Refer to the docs to learn more about useField
and the meta
object and how it is relevant to form validation:
The docs also publish a validation guide: