A comparison of formik, final-form and react-hook-form

Form is essential for every website on internet. Building form with ReactJS is not easy as React is just a UI library and it doesn’t provide form utility out of the box. So we have to rely on the ReactJS ecosystem to find a library to do the repetitive and hard work for us.

Choosing a library for your project is not easy as well. As an experienced ReactJS developer, you know that there are always a ton of libraries to choose from. And you have to read all of the documents and test them by yourself.

That’s why in this post, I will make a comparison of some well-known form libraries.

For handling form, we have these candidates:

NameGithub stars Weekly downloads Size
formik23.4K868K7.22 kB
final-form2.5K222K5.1 kB
react-form1.9K12K4.3 kB
react-hook-form12.5K270K8.67 kB
redux-form12.3K389K26.4 kB

That’s quite a lot for me to do the comparison, so in this post, I gonna compare formik, final-form, and react-hook-form.

There is no point to use redux-form as redux-form’s developer already replaced by final-form. Also, storing form data in Redux state is not a wise decision.


Development experience comparison

Formik

Based on the Github stars and weekly download, formik is the most well-known library for handling form. I have used formik in some projects and I have to admit it was the best library.

This is how you use formik in your component:

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

const Basic = () => (
  <div>
    <h1>Anywhere in your app!</h1>
    <Formik
      initialValues={{ email: "", password: "" }}
      validate={(values) => {
        const errors = {};
        if (!values.email) {
          errors.email = "Required";
        } else if (
          !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
        ) {
          errors.email = "Invalid email address";
        }
        return errors;
      }}
      onSubmit={(values, { setSubmitting }) => {
        setTimeout(() => {
          alert(JSON.stringify(values, null, 2));
          setSubmitting(false);
        }, 400);
      }}
    >
      {({
        values,
        errors,
        touched,
        handleChange,
        handleBlur,
        handleSubmit,
        isSubmitting,
        /* and other goodies */
      }) => (
        <form onSubmit={handleSubmit}>
          <input
            type="email"
            name="email"
            onChange={handleChange}
            onBlur={handleBlur}
            value={values.email}
          />
          {errors.email && touched.email && errors.email}
          <input
            type="password"
            name="password"
            onChange={handleChange}
            onBlur={handleBlur}
            value={values.password}
          />
          {errors.password && touched.password && errors.password}
          <button type="submit" disabled={isSubmitting}>
            Submit
          </button>
        </form>
      )}
    </Formik>
  </div>
);

export default Basic;

Formik is verbose and declarative, so you have to append Formik the render result. Although the code above is easy to understand for developers but I found some disadvantages:

  • You have to render Formik in the your component, it makes the render function longer and harder to read.
  • You have to pass handleChange, handleBlur, and input value into your input element (you have to use Field component to reduce these code)
  • You have to write your own validation method or use another validation library like joi or yup.
  • Formik re-render your component at every input change.
  • Formik provides a hook function, but it doesn’t work with Field, FieldArray, or ErrorMessage.

final-form

This library is made by the author redux-form. That’s why it fixed a lot of mistakes from Redux-Form. One of the mistakes is we should avoid store form state in Redux. There is no benefit from storing your form data in Redux state before the form is submitted.

The usage of final-form is very similar to formik:

import React from "react";
import { render } from "react-dom";
import Styles from "./Styles";
import { Form, Field } from "react-final-form";

const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

const onSubmit = async (values) => {
  await sleep(300);
  window.alert(JSON.stringify(values, 0, 2));
};

const App = () => (
  <Styles>
    <h1>React Final Form Example</h1>
    <h2>Password / Confirm Validation</h2>
    <a
      href="https://final-form.org/react"
      target="_blank"
      rel="noopener noreferrer"
    >
      Read Docs
    </a>
    <Form
      onSubmit={onSubmit}
      validate={(values) => {
        const errors = {};
        if (!values.username) {
          errors.username = "Required";
        }
        if (!values.password) {
          errors.password = "Required";
        }
        if (!values.confirm) {
          errors.confirm = "Required";
        } else if (values.confirm !== values.password) {
          errors.confirm = "Must match";
        }
        return errors;
      }}
      render={({ handleSubmit, form, submitting, pristine, values }) => (
        <form onSubmit={handleSubmit}>
          <Field name="username">
            {({ input, meta }) => (
              <div>
                <label>Username</label>
                <input {...input} type="text" placeholder="Username" />
                {meta.error && meta.touched && <span>{meta.error}</span>}
              </div>
            )}
          </Field>
          <Field name="password">
            {({ input, meta }) => (
              <div>
                <label>Password</label>
                <input {...input} type="password" placeholder="Password" />
                {meta.error && meta.touched && <span>{meta.error}</span>}
              </div>
            )}
          </Field>
          <Field name="confirm">
            {({ input, meta }) => (
              <div>
                <label>Confirm</label>
                <input {...input} type="password" placeholder="Confirm" />
                {meta.error && meta.touched && <span>{meta.error}</span>}
              </div>
            )}
          </Field>
          <div className="buttons">
            <button type="submit" disabled={submitting}>
              Submit
            </button>
            <button
              type="button"
              onClick={form.reset}
              disabled={submitting || pristine}
            >
              Reset
            </button>
          </div>
          <pre>{JSON.stringify(values, 0, 2)}</pre>
        </form>
      )}
    />
  </Styles>
);

render(<App />, document.getElementById("root"));

As you see, we need to render the Form component, and callbacks for onSubmit, validation. You also have to use <Field> component to pass the props to your input element.

Similar to formik, final-form doesn’t provide any validation method, you have write the validation by yourself. It doesn’t support joi or yup out of the box too, you need a work-around to make it work with joi and yup (https://github.com/final-form/react-final-form/issues/116)

react-hook-form

I already like hooks because they greatly reduce the amount of code I need to write.

I found react-hook-form on /r/reactjs. At that time, I mostly write the form and handle form validation by myself, it took me a lot of time to maintain so I decided to try this library.

import React from "react";
import { useForm } from "react-hook-form";

export default function App() {
  const { register, handleSubmit, watch, errors } = useForm();
  const onSubmit = data => console.log(data);

  console.log(watch("example")); // watch input value by passing the name of it

  return (
    {/* "handleSubmit" will validate your inputs before invoking "onSubmit" */}
    <form onSubmit={handleSubmit(onSubmit)}>
    {/* register your input into the hook by invoking the "register" function */}
      <input name="example" defaultValue="test" ref={register} />

      {/* include validation with required or other standard HTML validation rules */}
      <input name="exampleRequired" ref={register({ required: true })} />
      {/* errors will return when field validation fails  */}
      {errors.exampleRequired && <span>This field is required</span>}

      <input type="submit" />
    </form>
  );
}

My first impression with react-hook-form is the code is shorter, cleaner, and easy to understand. I just need to pass register method into my input element and the form just works.

It has some standard validation methods to help me quickly setup form validation. I can also use yup to create a schema and validate form.

Performance comparison

One of the key features of react-hook-form is it has the best performance. Actually, I never have trouble with my forms. Unless you are building a real-time data application then the overall React performance is okay for me.

But if you really about the performance then react-hook-form is the best because it reduces waste render cycle.


Conclusion

At this time of writing, react-hook-form is my favorite library for handle form. The only downside is it only works with functional component. So you can’t use it if the React version of your legacy project is smaller than 16.8.

Meanwhile, Formik is still a solid choice for you, especially if you have to work with class component.


JSLancer is web development studio, we build high-quality web applications with ReactJS, NodeJS, GraphQL, Redux.

Contact: david@jslancer.com