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:

Published by

@firxworx

Kevin Firko (@firxworx) is an entrepreneur, developer, and digital project manager based in Toronto, Canada. He works on ventures and client projects through Bitcurve, the independent digital design and development studio he founded in 2008.