Formik for React [Guide]

Published on

If you are working on any sized React app, you will quickly find a need for working with forms.

Simple forms are easy to manage with things like useState(). But once you have forms with multiple fields (of different types), validation rules, on submit logic and so on, then it becomes hard to manage.

Formik is one solution to these problems, and this is a guide on what you need to know to start using it.

Table of Contents

Installing formik and basic setup/usage

yarn add formik

For local demo you can play with Formik at https://codesandbox.io/s/zKrK5YLDZ

  • wrap your forms in <Formik>.
  • In that <Formik> component, pass in initialValues. You will probably also want to add a function for onSubmit, and maybe some validation rules.
  • Inside <Formik>, pass in a function as the child, which gives you props like values, handleChange etc.

Here is a set up (note: there are easier ways to set this up - explained in next section).

import React from 'react';
import { Formik } from 'formik';

const BasicFormik = () => (
<div>
 <Formik
   initialValues={{ name: '' }}
   validate={values => {
     const validationErrors = {}
     
     if(!values.name.trim()) {
        validationErrors.name = 'You must add a name'
     }
     if(values.name.length > 30) {
        validationErrors.name = 'Your name must be 30 or fewer characters'
     }
     
     return validationErrors;
   }}
   onSubmit={(values, { setSubmitting }) => {
     fetch(
       '/api/save-name', 
       {
        method: 'POST', 
        body: JSON.stringify({name: values.name})
        })
     .then(() => alert('saved'))
   }}
 >
   {({
     values,
     errors,
     touched,
     handleChange,
     handleSubmit,
   }) => (
     <form onSubmit={handleSubmit}>
       <input
         type="name"
         name="name"
         onChange={handleChange}
         value={values.name}
       />
       {errors.name && touched.name && <p>Validation error: {errors.name}</p>}
       
       <button type="submit">
         Save your name
       </button>
     </form>
   )}
 </Formik>
</div>
);

The handleChange function will look at event.target.name (from the <input name="email">), and will update values.name based on that.

There are other helpful functions you can add like handleBlur

Using Formik fields

In the previous example I manually added the event listeners, set the value of the input, and manually displayed error messages.

You can also use Formik's <Field> and <ErrorMessage> to be a bit cleaner.

import React from 'react';
import { ErrorMessage, Field, Formik, Form,  } from 'formik';

const WithHelperComponents = () => (
<div>
 <Formik
   initialValues={{ name: '' }}
   validate={values => {
     const validationErrors = {}
     
     if(!values.name.trim()) {
        validationErrors.name = 'You must add a name'
     }
     if(values.name.length > 30) {
        validationErrors.name = 'Your name must be 30 or fewer characters'
     }
     
     return validationErrors;
   }}
   onSubmit={(values, { setSubmitting }) => {
     fetch(
       '/api/save-name', 
       {
        method: 'POST', 
        body: JSON.stringify({name: values.name})
        })
     .then(() => alert('saved'))
   }}
 >
   {() => (
     <Form>
       <Field
         type="name"
         name="name"
       />
       <ErrorMessage name="email" component="div" />
       
       <button type="submit">
         Save your name
       </button>
     </form>
   )}
 </Formik>
</div>
);

This time I used the <Field> and <ErrorMessage>, which uses the name prop to set everything up (handleChange, handleBlur, etc on the input, and checking for validation error message)

Validation

In the previous examples I set up my own custom validation function, that returned an object like {name: "You must enter a name"} if there were validation errors.

The usual way of validation with Formik is to use a library such as Yup

Here is a demo:

 import React from 'react';
 import { Formik, Form, Field } from 'formik';
 import * as Yup from 'yup';
 
 const nameSchema = Yup.object().shape({
   name: Yup.string()
     .min(2, 'Name must be at least 2 characters')
     .max(30, 'Name must be at most 30 characters')
     .required('Name is required'),
 });
 
function ComponentWithValidationSchema() {
    return <Formik initialValues={{ name: ''}} validationSchema={nameSchema}>
        <Form> 
            <Field name="name" />
            <ErrorMessage name="name" />
            <button type="submit">Save name</button>
        </Form>
    </Formik>
}

Notes on validation

  • If you have your own custom validation function, return undefined if there is no error. Do not return null
  • Validation on <Field> or <FastField> will run after onChange (handleChange, setFieldValue, setFieldValues) and onBlur (handleBlur, setTouched, setFieldTouched)
    • you can change this - the main (root) <Formik> component accepts props like validationOnChange, validateOnBlur so you can disable if needed
  • Validation also runs on form submission (handleSubmit, submitForm) (any validation messages are merged with the top level validation error messages)
  • Validation on <Field> or <FastField> components runs only if the component is mounted. So if you have some fields which are not mounted (hidden on some multi stage wizard for example), they do not get validated
  • If you need to know if the form is running validation, use the isValidating prop (passed in to the child of <Formik>.
  • You can also manually run validation, by using the validateField() prop that is passed in to the child of <Formik>

function validateName(name) {
  if(!name) return 'Name is required';
  if(name.length < 2) return 'Name must be at least 2 characters';
}
 function ManualValidation() {
   return (
     <div>
       <Formik
         initialValues={{ name: '', }}
         onSubmit={console.log}
       >
         {({ errors, touched, validateField, validateForm }) => (
           <Form>
             <Field name="name" validate={validateName} />
             {errors.name && touched.name && <div className="alert">{errors.name}</div>}

             <button type="button" onClick={() => validateField('name')}>
               Validate the name field
             </button>

             <button
               type="button"
               onClick={async () => {
                 await validateForm()
               }}
             >
               Validate entire form
             </button>
             <button type="submit">Save name</button>
           </Form>
         )}
       </Formik>
     </div>
   )
 }

Use of Formik with class based components/HoC

All examples on my guide deal with functional components, as that is the more modern way that the majority of new apps are using. But Formik of course works with class based components.

You can also use withFormik to pass in props/form handlers into a component (class based or functional).

Example:

 import React from 'react';
 import { withFormik } from 'formik';
 
 const YourFormComponent = props => {
   const {
     values,
     touched,
     errors,
     handleChange,
     handleBlur,
     handleSubmit,
   } = props;
   return (
     <form onSubmit={handleSubmit}>
       <input
         name="name"
         onBlur={handleBlur}
         onChange={handleChange}
         type="text"
         value={values.name}
       />
       {errors.name && touched.name && <div className="alert">{errors.name}</div>}
       <button type="submit">Save name</button>
     </form>
   );
 };
 
 const YourFormWithFormikComponent = withFormik({
   mapPropsToValues: () => ({ name: '' }),
 
   validate: values => {
     const validationErrors = {}

     if(!values.name.trim()) {
       validationErrors.name = 'You must add a name'
     }
     if(values.name.length > 30) {
       validationErrors.name = 'Your name must be 30 or fewer characters'
     }

     return validationErrors;
   },
 
   handleSubmit: (values) => 
     fetch(
       '/api/save-name',
       {
         method: 'POST',
         body: JSON.stringify({name: values.name})
       })
       .then(() => {
         alert('saved');
       })
   ,
 })(YourFormComponent);

Submit function

You can pass in a handleSubmit function.

This has a couple of arguments - the first is the values from the user input for your form. The second is the FormikBag, which includes things such as:

  • setValues - you can use this to set new form values
  • setErrors - if you need to set some errors for specific fields
  • setSubmitting - use this to set submitting as false. Note: if your handleSubmit function is async, then setSubmitting(false) is automatically called after the promise resolves

and more - see section on FormikBag

Working with arrays of fields or nested objects

The examples so far have been very trivial, and would be easy to implement without Formik. But I think one area that Formik is very useful is when your fields are more complex, such asi if you have nested fields or arrays of fields (which may change is length of the array).

You can set up nested objects within initialValues exactly how you would with any normal plain old JS object:

<Formik
   initialValues={{
     profile: {
       firstName: '',
       lastName: '',
     },
     votes: 0,
   }}
>...</Formik>

You can then reference them in <Field>, <FastField> and <ErrorMessage> with dot notation:

<Formik
  initialValues={{
    profile: {
      firstName: '',
      lastName: '',
    },
    votes: 0,
  }}
>
  <Form>
      <Field name="profile.firstName" />
      <ErrorMessage name="profile.firstName" />
  </Form>
</Formik>

If you have an array of values, it is quite simlar. Instead of referencing them with dot notation, you write it out like name="names[0]"

<Formik
  initialValues={{
    names: ['bart', 'lisa', 'marge'],
  }}
>
  <Form>
    <Field name="names[0]" />
    <Field name="names[1]" />
    <Field name="names[2]" />
  </Form>
</Formik>

If you need to manipulate the array, then you can use the <FieldArray> component. You pass in a render prop, which can be a function that takes an argument of helper functions (such as remove(atIndex) to remove an item, or insert(atIndex, newValue))

import React from 'react';
import { Formik, Form, Field, FieldArray } from 'formik';

export const NamesList = () => (
 <Formik
   initialValues={{ names: ['bart', 'lisa', 'marge'] }}
   onSubmit={console.log}
   render={({ values }) => (
     <Form>
       <FieldArray
         name="names"
         render={arrayHelpers => (
           <div>
             {values.names && values.names.length > 0 && (
               values.names.map((friend, index) => (
                 <div key={index}>
                   <Field name={`names.${index}`} />
                   <button
                     type="button"
                     onClick={() => arrayHelpers.remove(index)} 
                   >
                     remove
                   </button>
                 </div>
               ))
             )}
             
             <button type="button" onClick={() => arrayHelpers.push('')}>
               Add new name
             </button>
             
             <button type="submit">Submit</button>
           </div>
         )}
       />
     </Form>
   )}
 />
);

You can pass in a couple of props to <FieldArray>: name (the field name, from your values object), and an optional boolean validateOnChange. Set it to false to not run validation after values in the array change.

If your values is an array of objects, you can reference them like this:

<Formik
  initialValues={{people: [{name: 'Bart', age: 10}, {name: 'marge', age: 40}]}}>
    <Form>
       <FieldArray
         name="people"
         render={arrayHelpers => (
           <div>
             <p>There are {values.people.length} people you can edit:</p>
             
             {values.people.map((friend, idx) => (
               <div key={index}>
                 <p>Person at index: {idx}</p>
                 
                 <Field name={`people[${index}].name`} />
                 <Field name={`people[${index}].age`} />
                 
                 <button type="button" onClick={() => arrayHelpers.remove(idx)}>
                   Remove this person
                 </button>
               </div>
             ))}
             <button
               type="button"
               onClick={() => arrayHelpers.push({ name: '', age: '' })}
             >
               Add new person
             </button>
           </div>
         )}
       />
     </Form>
</Formik>

Array functions in FieldArray

You have access to a bunch of typical array functions

  • insert: (insertAtIndex: number, value: any) => void inset a new value at a specific index
  • move: (fromIndex: number, toIndex: number) => void move the value from an old index to a new index
  • pop<T>(): T | undefined Pop an item off the end of the array (removing it from the array), and return it
  • push: (newObject: any) => void push a new value to the end of the array
  • remove<T>(atIndex: number): T | undefined Remove an item at index (and return it)
  • replace: (atIndex: number, updatedValue: any) => void Replace a value at index
  • swap: (indexA: number, indexB: number) => void swap the values of 2 items at certain indexes
  • unshift: (newValue: any) => number add a new item to the array at the start (and return the new length of the array)

How to render (children/inner component) in FieldArray

There are 3 ways to render your content inside FieldArray. With component=, render=, or with children


function YourCustomComponent({insert, pop}) { return <Form>todo</Form>; }

<FieldArray
  name="friends"
  component={YourCustomComponent}
/>


 <FieldArray
  name="people"
  render={({ move, swap, push, insert, unshift, pop }) => (
    <Form>
      // ...
    </Form>
  )}
/>

<FieldArray name="friends">
  {({ move, push, insert, pop, form }) =>
          (
                  <Form>
                    {/*... use these however you want */}
                  </Form>
          )
  }}
</FieldArray>

Validation in FieldArray

Validation for nested objects is a bit more tricky (with yup).


const validationSchema = Yup.object().shape({
people: Yup.array()
 .of(
   Yup.object().shape({
     name: Yup.string().min(2, 'too short').required('Required'), 
     age: Yup.string().min(0, 'must have been born').required('Required'), 
   })
 )
 .required('Must have some people') 
 .min(3, 'Minimum of 3 people'),
});

But there is an issue with that: the of() part of validation will run (and will return a nested object with those errors). But if they pass ok (no errors in the of() block, then the other rules (the required() and min()) will run. These will return a string.

So if you want to show error messages for entire form (must have min of 3 people), you have to check the error object is a string.


// won't work: it can be an object if one of the names is under 2 characters
const thisWontWork = !!errors.people && <div>Error (you have to update # of people): {errors.people}</div>;

const thisWillWork = typeof errors.people === 'string' && <div>Error (you have to update # of people): {errors.people}</div>;

Submitting

There are a few ways to handle a submit, but the typical way is to pass in a handleSubmit={(values) => console.log(values)} sort of function to the parent <Formik>.

When submit runs:

  • Formik will touch all fields
    • why? so that if you have logic that checks/shows errors only after a field was touched, it will now be visible to users. Often there is logic like errors.name && touched.name && <div>Error with name field: {errors.name}</div>
  • set isSubmitting = true
  • increment submitCount by 1
  • Runs validation:
    • set isValidating = true
    • Runs the validate function on fields
    • If any errors: stop validation. Sets isValidating and isSubmitting to false, your errors will be set
    • If no errors: isValidating to false, and continues
  • Then it runs your onSubmit or handleSubmit function
  • Once done, if your submit handler was async it will set setSubmitting(false). You can also call this yourself in your submit handler.

You should disable submitting when isSubmitting is true. Either in your submit handler, or via disabled on form / submit buttons.

useField hook

FastField

Using Formik with other libraries

FormikBag

On a lot of the handler functions (such as onReset, onSubmit, etc) you often get 2 arguments: the first is an object of your field values, and the second is a FormikBag one.

This object has the following in it:

  • props (props passed to the wrapped component)

  • resetForm

  • setErrors

  • setFieldError

  • setFieldTouched

  • setFieldValue

  • setStatus

  • setSubmitting

  • setTouched

  • setValues

  • For validation there is extremely good support for Yup

  • UIs such as MaterialUI and Ant have great support too.

Checkboxes

Checkboxes are easy to use in Formik. Use their <Field> component like this:

<Field type="checkbox" name="published" value="true" />

If you have multiple checkboxes with the same name, they get combined into an array

<Field type="checkbox" name="animal" value="cat" />
<Field type="checkbox" name="animal" value="bird" />

Radio inputs

Very similar to checkboxes.

<Field type="radio" name="tshirtSize" value="small" />
<Field type="radio" name="tshirtSize" value="medium" />
<Field type="radio" name="tshirtSize" value="large" />

Dependant Fields (derived values)

I find dependant fields the one thing that is a bit awkward at times to work with, as you can easily get to infinite re-render states.

On paper its quite easy - use useEffect() with the values you want to use as dependancies, and call setFieldValue in there.

Their docs have a good and easy to understand demo here

ErrorMessage

You can manually access the errors object, which can contain the error messages. But you often want to only show error messages once the user has interacted with a field (when touched[fieldName] === true).

There is a nice helper component called ErrorMessage that handles this (as long as the error messages are strings).

This will show error message from the 'name' field:

<ErrorMessage name="name" />

If you want to customize the error message, pass in a function as the children:

<ErrorMessage name="name">{errorMessage => <div className="alert">{errorMessage}</div>}</ErrorMessage>

Or you can pass in a component:

<ErrorMessage name="name" component="li" />
<ErrorMessage name="name" component={SomeCustomComponent} />