Tutorials

Make React Forms Quick and Simple With Formik21 min read

February 12, 2020 13 min read
Avatar

author:

Make React Forms Quick and Simple With Formik21 min read

Reading Time: 13 minutes

Formik is a small library that helps us deal with the verboseness of making forms with React.

The major problems when building forms are:

  • Setting up state for the form values, the form errors and the form validity
  • Handling the user inputs and updating the state accordingly
  • Creating validations and showing user-friendly error messages
  • Handling the submission of the form

Doing this the natural “React” way is a time-consuming and can get monotonous rather quickly.

Luckily, Formik can do all this for us.

What Are We Building

In this tutorial,we will build a simple signup form with React, Formik and Yup (Yup is purely optional and all examples would work without it).

It will consist of four form controls and two buttons.

This is how the form looks:

Before We Start

You will need to have familiarity with HTML, JavaScript (including ES6 features) and React (including React Hooks) to be able to fully grasp Formik and its magic.

Setup

For this example I am going to set up a local development environment, using Visual Studio Code (you can use an editor of your choice) along with create-react-app.

You can also proceed that way or use this set up CodeSandbox playground.

Here are the steps to follow along if you decide to use a local development environment.

  1. Make sure you have a recent version of Node.js installed.
  2. Follow the installation instructions for Create React App to make a new project.
  3. You can remove some of the files in the “src” folder of the project and leave only the ones we will be using.

 

This is how my App.css file looks, it includes some basic styling to make the form better looking,you can use any styling you like and it is purely optional.

p {
color: red;
}

.input-error {
border: 1px solid red;
}

input, select {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
display: block;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}

button[type=submit] {
width: 100%;
background-color: #4CAF50;
color: white;
padding: 14px 20px;
margin: 14px 0;
border: none;
border-radius: 4px;
cursor: pointer;
}

button[type=reset] {
width: 100%;
background-color: slategray;
color: white;
padding: 14px 20px;
margin: 14px 0;
border: none;
border-radius: 4px;
cursor: pointer;
}

button[type=submit]:hover {
background-color: #45a049;
}

button[type=submit]:disabled {
background-color: #49804c;
}

button[type=reset]:hover {
background-color: rgb(95, 107, 119);
}

div {
border-radius: 5px;
background-color: #f2f2f2;
padding: 20px;
}

And this is how App.js looks:

import React from 'react';
import './App.css';

function App() {
  return (
    <div className="App">
      {/* This is where our form component will be imported. */}
    </div>
  );
}

export default App;
  1. And for the last step we need to install Formik,which can be done the following way:
npm install formik --save
//or
yarn add formik

Formik In Action

There are a couple of ways of using Formik,we are going to start with the most basic one because it will help us see how Formik builds on itself.

Our form will have 3 <input> fields (“email”,”username”,”password”) and one <select> field – “gender”, we will also include some basic validation.

Example With The useFormik() Hook

We are going to create a file named SignupForm.js and this is how it will look like:

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

export const SignupForm = () => {
    const formik = useFormik({
        initialValues: {
            email: '',
            username: '',
            password: '',
            gender: 'Male'
        },
        onSubmit: values => {
            alert(JSON.stringify(values,null,1));
        }
    });

    return (
         <form onSubmit={formik.handleSubmit}>
            <label htmlFor='email'>Email Address</label>
            <input
                id="email"
                name="email"
                type="email"
                onChange={formik.handleChange}
                value={formik.values.email}
            />
            <label htmlFor='username'>Userame</label>
            <input
                id="username"
                name="username"
                type="text"
                onChange={formik.handleChange}
                value={formik.values.username}
            />
            <label htmlFor='password'>Password</label>
            <input
                id="password"
                name="password"
                type="password"
                onChange={formik.handleChange}
                value={formik.values.password}
            />
            <label htmlFor='gender'>Gender</label>
            <select
                name="gender"
                onChange={formik.handleChange}
                value={formik.values.gender}
            >
                <option value="male">Male</option>
                <option value="female">Female</option>
                <option value="other">Other</option>
            </select>
            <button type="submit">Submit</button>
        </form>
    );
}

On a first glance we have a function component which makes use of the useFormik() hook to which we pass an object with our form initial values and a onSubmit() function for handling the form submission.

useFormik() is a custom React hook that will return an object containing the form state and some helper functions.

These are all the properties it provides:

The following are these which concern us now:

values – our form current values

handleChange – a change handler to pass to each <input>,<select> or <textarea>

handleSubmit – an submission handler

We reuse the formik.handleChange() function for each form field.

We pass an id and name HTML attribute that matches the property we defined in initialValues.

We access the field’s value using the same name (username -> formik.values.username)

That is all we need to have a working React form with Formik.

The form is working now, it can be submitted but it lacks validations.

I mentioned in the beginning that Formik can also help with error messages and validation lets check that out.

Validation

We can write a custom validation function and we have to pass it as validate to the useFormik() hook.

This custom validation function should produce an error object with a matching shape to our values so the formik.errors object can be populated.

Per Formik docs:

By default, Formik will validate after each keystroke, each input’s blur event, as well as prior to submission. It will only proceed with executing the onSubmit function we passed to useFormik() if there are no errors (i.e if our validation function returned {}).

 

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

export const SignupForm = () => {
    const emailRegexPattern = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i;

    const validate = values => {
        const errors = {};

        if (!values.email) {
            errors.email = 'Required';
        } else if (!emailRegexPattern.test(values.email)) {
            errors.email = 'Invalid email adress';
        }

        if (!values.username) {
            errors.username = 'Required';
        } else if (values.username.length > 20) {
            errors.username = 'Username must not be longer than 20 characters';
        }

        if (!values.password) {
            errors.password = 'Required';
        } else if (values.password.length < 6) {
            errors.password = 'Password must be atleast 6 characters';
        }

        if (!values.gender) {
            errors.gender = 'Required';
        }

        return errors;
    }

    const formik = useFormik({
        initialValues: {
            email: '',
            username: '',
            password: '',
            gender: 'Male'
        },
        validate,
        onSubmit: values => {
            alert(JSON.stringify(values, null, 1));
        }
    });

    return (
        <form onSubmit={formik.handleSubmit}>
            <label htmlFor='email'>Email Address</label>
            <input
                id="email"
                name="email"
                type="email"
                onChange={formik.handleChange}
                value={formik.values.email}
            />
            {formik.errors.email && <p>{formik.errors.email}</p>}
            <label htmlFor='username'>Userame</label>
            <input
                id="username"
                name="username"
                type="text"
                onChange={formik.handleChange}
                value={formik.values.username}
            />	
            {formik.errors.username && <p>{formik.errors.username}</p>}
            <label htmlFor='password'>Password</label>
            <input
                id="password"
                name="password"
                type="password"
                onChange={formik.handleChange}
                value={formik.values.password}
            />
            {formik.errors.password && <p>{formik.errors.password}</p>}
            <label htmlFor='gender'>Gender</label>
            <select
                name="gender"
                onChange={formik.handleChange}
                value={formik.values.gender}
            >
                <option value="male">Male</option>
                <option value="female">Female</option>
                <option value="other">Other</option>
            </select>
            {formik.errors.gender && <p>{formik.errors.gender}</p>}
            <button type="submit">Submit</button>
        </form>
    );
}

My validate function is pretty simple, if a field value does not fulfill the condition for it we create a property with the field name as a key and a custom error message as a value in the errors object.
Then if there is an error it is shown under the form field in a paragraph.

 

The problem with how our validation works now is that it is far from user-friendly.As mentioned earlier our validation runs on each keystroke against all of the form values and because of that the errors object contains all validation errors all the time. As seen in the image above that way error messages are being shown even for fields that the user have not visited yet. In most cases we want to show a field’s error message after the user is done typing in that field.

If we were building a form using plain React we would have to create some kind of flags with some logic behind them which were going to decide when to show the error messages.

Formik provides us this information in its touched object, which holds key-value pairs like field name – true/false.

Here Formik function handleBlur also comes to play as it can helps us keep track whether an input has been touched or not. As handleChange it uses the “name” attribute to figure out which field to update.

The onblur event occurs when an object loses focus.

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

export const SignupForm = () => {
    const emailRegexPattern = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i;


    const validate = values => {
        const errors = {};

        if (!values.email) {
            errors.email = 'Required';
        } else if (!emailRegexPattern.test(values.email)) {
            errors.email = 'Invalid email adress';
        }

        if (!values.username) {
            errors.username = 'Required';
        } else if (values.username.length > 20) {
            errors.username = 'Username must not be longer than 20 characters';
        }

        if (!values.password) {
            errors.password = 'Required';
        } else if (values.password.length < 6) {
            errors.password = 'Password must be atleast 6 characters';
        }

        if (!values.gender) {
            errors.gender = 'Required';
        }

        return errors;
    }

    const formik = useFormik({
        initialValues: {
            email: '',
            username: '',
            password: '',
            gender: 'Male'
        },
        validate,
        onSubmit: values => {
            alert(JSON.stringify(values, null, 1));
        }
    });

  return (
    <form onSubmit={formik.handleSubmit}>
      <label htmlFor='email'>Email Address</label>
      <input
        id="email"
        name="email"
        type="email"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.email}
      />
      {formik.errors.email && formik.touched.email &&
        <p>{formik.errors.email}</p>}
      <label htmlFor='username'>Userame</label>
      <input
        id="username"
        name="username"
        type="text"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.username}
      />
      {formik.errors.username && formik.touched.username &&
        <p>{formik.errors.username}</p>}
      <label htmlFor='password'>Password</label>
      <input
        id="password"
        name="password"
        type="password"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.password}
      />
      {formik.errors.password && formik.touched.password &&
        <p>{formik.errors.password}</p>}
      <label htmlFor='gender'>Gender</label>
      <select
        name="gender"
        onChange={formik.handleChange}
        onBlur={formik.handleBlur}
        value={formik.values.gender}
      >
        <option value="male">Male</option>
        <option value="female">Female</option>
        <option value="other">Other</option>
      </select>
      {formik.errors.gender && formik.touched.gender &&
        <p>{formik.errors.gender}</p>}
      <button type="submit">Submit</button>
    </form>
  );
}

We pass formik.handleBlur to each input’s onBlur prop and tweak our error message render logic to only show a field’s error message if the error exists and the field has been visited.

Yup

Formik is not strongly opinionated about validation,it is left up to your personal choice, you can write your own validators or use a helper library.
Yup is a library for object schema validation.
With it we can define a schema which resembles our intended schema for an object and check if our data object matches this schema hence validating it.
Yup schema can be very expressive (which we will see in a little bit) and allow interdependent validations(stacking conditions based on other values through out your data) or custom rules.

Here is a link to Yup docs if you have not met Yup until now and you want to know more about it.

The reason why exactly Yup make its way in this article is that Formik has a special configuration option / prop for Yup called validationSchema which will transform Yup’s validation errors messages into a pretty object whose keys match values/initialValues/touched.

You can install Yup like:

npm install yup --save
//or
yarn add yup
import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';

export const SignupForm = () => {
    const formik = useFormik({
        initialValues: {
            email: '',
            username: '',
            password: '',
            gender: ''
        },
        validationSchema: Yup.object({
            email: Yup.string()
                .required('This field is required')
                .email('Invalid email address'),
            username: Yup.string()
                .required('This field is required')
                .max(10, 'Username must be 20 characters or less'),
            password: Yup.string()
                .required('This field is required')
                .min(4, 'Password must be atleast 6 characters'),
            gender: Yup.string()
                .required('This field is required')
        }),
        onSubmit: values => {
            alert(JSON.stringify(values));
        }
    });

    return (
       <form onSubmit={formik.handleSubmit}>
            <label htmlFor='email'>Email Address</label>
            <input
                id="email"
                name="email"
                type="email"
                className={formik.errors.email &&
                    formik.touched.email ? 'input-error' : ''}
                onChange={formik.handleChange}
                onBlur={formik.handleBlur}
                value={formik.values.email}
            />
            {formik.errors.email && formik.touched.email &&
                <p>{formik.errors.email}</p>}
            <label htmlFor='username'>Username</label>
            <input
                id="username"
                name="username"
                type="text"
                className={formik.errors.username &&
                    formik.touched.username ? 'input-error' : ''}
                onChange={formik.handleChange}
                onBlur={formik.handleBlur}
                value={formik.values.username}
            />
            {formik.errors.username && formik.touched.username &&
                <p>{formik.errors.username}</p>}
            <label htmlFor='password'>Password</label>
            <input
                id="password"
                name="password"
                type="password"
                className={formik.errors.password &&
                    formik.touched.password ? 'input-error' : ''}
                onChange={formik.handleChange}
                onBlur={formik.handleBlur}
                value={formik.values.password}
            />
            {formik.errors.password && formik.touched.password &&
                <p>{formik.errors.password}</p>}
            <label htmlFor='gender'>Gender</label>
            <select
                name="gender"
                className={formik.errors.gender &&
                    formik.touched.gender ? 'input-error' : ''}
                onChange={formik.handleChange}
                onBlur={formik.handleBlur}
                value={formik.values.gender}
            >
                <option value="male">Male</option>
                <option value="female">Female</option>
                <option value="other">Other</option>
            </select>
            {formik.errors.gender && formik.touched.gender &&
                <p>{formik.errors.gender}</p>}
            <button type="submit">Submit</button>
        </form>
    );
}

As you can see the only change from our previous code is that instead of using the validate configuration option and passing to it our custom validation function we now use validationSchema configuration option.
Looking at the Yup part of the code it can be seen that it is very expressive and intuitive.
What is more we also managed to express the same validation logic from our validation function with way less code.

getFieldProps()

So our form works fine right now, the validation is organized and readable but it still feels like a lot of writing. Another useFormik() helper method can save us a little time. Given the field name,it returns to us the group of onChange, onBlur and value for a given field.Then we can spread(using the “…” operator) that on an input, select or textarea.

import React from 'react';
import { useFormik } from 'formik';
import * as Yup from 'yup';

export const SignupForm = () => {
    const formik = useFormik({
        initialValues: {
            email: '',
            username: '',
            password: '',
            gender: 'Male'
        },
        validationSchema: Yup.object({
            email: Yup.string()
                .required('This field is required')
                .email('Invalid email address'),
            username: Yup.string()
                .required('This field is required')
                .max(10, 'Username must be 20 characters or less'),
            password: Yup.string()
                .required('This field is required')
                .min(4, 'Password must be atleast 6 characters'),
            gender: Yup.string()
                .required('This field is required')
        }),
        onSubmit: values => {
            alert(JSON.stringify(values));
        }
    });

    return (
       <form onSubmit={formik.handleSubmit}>
            <label htmlFor='email'>Email Address</label>
            <input
                name="email"
                type="email"
                {...formik.getFieldProps('email')}
                className={formik.errors.email && formik.touched.email ?
                    'input-error' : ''}
            />
            {formik.errors.email && formik.touched.email &&
                <p>{formik.errors.email}</p>}
            <label htmlFor='username'>Username</label>
            <input
                name="username"
                type="text"
                {...formik.getFieldProps('username')}
                className={formik.errors.username && formik.touched.username ?
                    'input-error' : ''}
            />
            {formik.errors.username && formik.touched.username &&
                <p>{formik.errors.username}</p>}
            <label htmlFor='password'>Password</label>
            <input
                name="password"
                type="text"
                {...formik.getFieldProps('password')}
                className={formik.errors.password && formik.touched.password ?
                    'input-error' : ''}
            />
            {formik.errors.password && formik.touched.password &&
                <p>{formik.errors.password}</p>}
            <label htmlFor='gender'>Gender</label>
            <select
                name="gender"
                {...formik.getFieldProps('gender')}
                className={formik.errors.gender && formik.touched.gender ?
                    'input-error' : ''}
            >
                <option value="male">Male</option>
                <option value="female">Female</option>
                <option value="other">Other</option>
            </select>
            {formik.errors.gender && formik.touched.gender &&
                <p>{formik.errors.gender}</p>}
            <button type="submit">Submit</button>
        </form>
    );
}

The <Formik /> Component

Formik provides us with another option to build forms and this is done with the <Formik /> component. It uses the render props technique made popular by libraries like React Router. Here is a link to the React docs if you want to learn more about it.
React Context API also comes to play with the <Formik /> component and the other helper components that <Formik /> provides us with and which we will use now.
I am talking about <Form />, <Field /> and <ErrorMessage />.
More specifically they use React Context implicitly to connect to the parent <Formik /> state/methods.
Now we can swap the useFormik() hook with the <Formik /> component.

import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';

export const SignupForm = () => {
    return (
        <Formik
            initialValues={{
                email: '',
                username: '',
                password: '',
                gender: 'Male'
            }}
            validationSchema={Yup.object({
                email: Yup.string()
                    .required('This field is required')
                    .email('Invalid email address'),
                username: Yup.string()
                    .required('This field is required')
                    .max(20, 'This field must be 20 characters or less'),
                password: Yup.string()
                    .required('This field is required')
                    .min(6, 'This field must be atleast 6 characters'),
                gender: Yup.string()
                    .required('This field is required')
            })}
            onSubmit={(values, formikBag) => {
                setTimeout(() => {
                    alert(JSON.stringify(values, null, 1));
                    formikBag.setSubmitting(false)
                }, 1000);
            }}
        >
            {formik => (
                <Form>
                    <label htmlFor="email">Email</label>
                    <Field name="email" type="email" />
                    <ErrorMessage name="email" component="p" />
                    <label htmlFor="username">Username</label>
                    <Field name="username" type="text" />
                    <ErrorMessage name="username" component="p" />
                    <label htmlFor="password">Password</label>
                    <Field name="password" type="password" />
                    <ErrorMessage name="password" component="p" />
                    <label htmlFor="gender">Gender</label>
                    <Field name="gender" as="select">
                        <option value="male">Male</option>
                        <option value="female">Female</option>
                        <option value="other">Other</option>
                    </Field>
                    <ErrorMessage name="gender" component="p" />
                    <button type="submit" disabled={formik.isSubmitting}>Submit</button>
                    <button type="reset">Reset</button>
                </Form>
            )}
        </Formik>
    );
}

Instead of passing an object to useFormik() with initialValues,validationSchema and onSubmit keys and their corresponding values we pass them as props to the <Formik /> component.
The <Formik /> component calls useFormik() internally.
<Form /> is a small wrapper around an HTML <form> element that hooks into Formik’s handleSubmit and handleReset.All other props are passed directly through to the DOM node.

//to get a better idea
<Form />

// this is how <Form /> looks like underneath.
<form onReset={formikProps.handleReset} onSubmit={formikProps.handleSubmit} {...props} />

<Field /> will automatically hook up inputs to Formik.It uses the name attribute to match up with Formik state.<Field /> will default to an HTML <input /> element.

If we want to use the <Field /> component for a select field,we have to add the “as” prop with value select and we can pass the options of the <select> as children to the <Field /> component.

<ErrorMessage /> will render the error message of a given field if that field has been visited (i.e touched[name] === true) and there is an error message present. It can take a component prop that can be either a React component or the name of an HTML element to render. If not specified, <ErrorMessage /> will just return a string.

You may notice that we also added a condition for disabling the submit button – formik.isSubmitting which gets set to true automatically when a submission is attempted.And after the form is submitted and our submission handler we passed to onSubmit gets invoked we make use of the helper method formikBag.setSubmitting which will set isSubmitting to false and make the submit button available again.

useField() Hook

The useField() hooks helps us hook up inputs to Formik.
Formik creators encourage us to use it to build our own custom input components.
It accepts either a string of a field name or an object as an argument.The object must at least contain a name key. This object should be identical to the props that you would pass to <Field /> and the values and functions in fieldProps will mimic the behavior of <Field /> exactly. This is useful and generally preferred,since it allows you to take advantage of Formik’s checkbox, radio and multiple select behavior when the object contains the relevant key/values (e.g. type:’checkbox’, multiple:true etc.)
useField() returns to us an array with three elements – fieldProps, fieldMetaProps and fieldHelperProps.

In the above paragraph we went through what fieldProps looks like.
fieldMetaProps contains values which can helps with the logic for showing the field errors.
fieldHelperProps contains helper functions that allow us to imperatively change a field’s values.

import React from 'react';
import { Formik, Form, useField } from 'formik';
import * as Yup from 'yup';

export const SignupForm = (props) => {
    const MyTextInput = ({ label, ...props }) => {
        const [field, meta] = useField(props);
        return (
            <React.Fragment>
                <label htmlFor={props.id || props.name}>{label}</label>
                <input {...field} {...props} />
                {meta.touched && meta.error && (<p>{meta.error}</p>)}
            </React.Fragment>
        );
    }

    const MySelect = ({ label, ...props }) => {
        const [field, meta] = useField(props);
        return (
            <React.Fragment>
                <label htmlFor={props.id || props.name}>{label}</label>
                <select name={props.name} {...field} {...props}>
                    {props.children}
                </select>
                {meta.touched && meta.error && (<p>{meta.error}</p>)}
            </React.Fragment>
        );
    }

    return (
        <Formik
            initialValues={{
                email: '',
                username: '',
                password: '',
                gender: ''
            }}
            validationSchema={Yup.object({
                email: Yup.string()
                    .required('This field is required')
                    .email('Invalid email address'),
                username: Yup.string()
                    .required('This field is required')
                    .max(20, 'This field must be 20 characters or less'),
                password: Yup.string()
                    .required('This field is required')
                    .min(6, 'This field must be atleast 6 characters'),
                gender: Yup.string()
                    .required('This field is required')
            })}
            onSubmit={(values, { setSubmitting }) => {
                setTimeout(() => {
                    alert(JSON.stringify(values, null, 1));
                    setSubmitting(false);
                }, 1000);
            }}
        >
            {formik => (
                <Form>
                    <MyTextInput
                        label="Email"
                        name="email"
                        type="email"
                    />
                    <MyTextInput
                        label="Username"
                        name="username"
                        type="text"
                    />
                    <MyTextInput
                        label="Password"
                        name="password"
                        type="password"
                    />
                    <MySelect name="gender" label="Gender">
                        <option value="male">Male</option>
                        <option value="female">Female</option>
                        <option value="other">Other</option>
                    </MySelect>
                    <button disabled={formik.isSubmitting || Object.keys(formik.errors).length} type="submit">Submit</button>
                    <button type="reset">Reset</button>
                </Form>
            )}
        </Formik>
    );
}

That way we can still get the benefits Formik provides and create custom components adding additional things we may need for our form fields.

Cocnlusion

To wrap up we were able to successfully create a signup form that solves all of the problems mentioned in the beginning of the article with Formik doing most of the work for us:

  • Setting up state for the form values, the form errors and the form validity ✔
  • Handling the user inputs and updating the state accordingly ✔
  • Creating validations and showing user-friendly error messages at the correct time ✔
  • Handling the submission of the form ✔

If you want to check and play with the final result with custom form control components you can do that here or if you want to do that with the example that uses Formik <Field /> and <ErrorMessage /> components.

Formik Github if you want to look what Formik does under the hood.

Lastly, a link to Formik official page where in the docs you can find more about Formik and can check the API Reference which can be really useful and provide in-depth look into Formik.