React Forms Comparison

A quick overview of React Forms

Before we get to the main course, I’m going to present to you some dry facts about each of these libraries.

React Final Form

The first commit of React Final Form was released on the 27th of November 2017. Its main goal was to fix complaints developers had with the Redux Form. It has been in active development since then. So far it’s earned over 5,000 stars on GitHub, with 80 contributors. It’s been developed in JavaScript and it uses Final Form under the hood with both of these at just 8.1kB gzipped combined. It doesn’t have any other dependencies than that and, of course, React.

Formik

Formik, just as React Final Form, was created to address some of the issues people had with Redux Form. It was developed in TypeScript and released in July 2018 and since then it grew to be the biggest out of the three with almost 20,000 stars on GitHub. It’s also the biggest package, with almost 15kB gzipped in size and 8 dependencies.

React Hook Form

React Hook Form is the youngest of the bunch, being only 9 months old. It was released a month after React 16.8, aiming to utilize the hooks that were introduced in that update. It grew rapidly to 5,000 stars and 46 contributors. Like Formik, it was developed in TypeScript. With no dependencies and only 5.7kB gzipped in size, it makes it an appealing alternative to the bigger players out there. The author  also provides developers with a form builder, and while it may not be the perfect solution for every problem, it can supply you with a basic HTML form template and validations.

What are the differences?

With all the theory out of the way, let’s get to the meat and potatoes of this article. 
Below, I’ll do my best to present and compare various features and functionalities of our contenders. Let’s start with the basics.

Before we start though, I’ll introduce all the different utility functions I’ve used in all three libraries because they’re used in all three examples.

validators.js 

import { emailPattern } from 'utils/patterns';

export const required = (value) => (value ? undefined : 'Field is required');
export const mustBeNumber = (value) => (isNaN(value) ? 'Field must be a number' : undefined);
export const email = (value) => (emailPattern.test(value) ? undefined : 'Invalid email address');

composeValidators.js

export const composeValidators = (...validators) => (value) => (
validators.reduce((error, validator) => error || validator(value), undefined)
);

patterns.js

export const emailPattern =
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;

sleep.js

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

 

Form wrapper component, registering fields, validations, and error handling

React Final Form 

<Form
onSubmit={onSubmit}
initialValues={{
middleName: 'Paul',
gender: 'male',
}}
>
{({
handleSubmit,
submitting,
pristine,
form,
values,
submitError,
}) => (

<form onSubmit={handleSubmit}>
<Field
name="firstName"
validate={required}
>
{({ input, meta }) => (
<>
<div className="input-group">
<label htmlFor="firstName">First name</label>
<input
name={input.name}
onBlur={input.onBlur}
onChange={input.onChange}
value={input.value}
type="text"
placeholder="First name"
/>
</div>
{meta.error && meta.touched && (<div className="error">{meta.error}</div>)}
</>
)}
</Field>


You have to wrap your HTML form in a Form component to get access to props that React Final Form provides for you. Then, every input needs to be wrapped in the Field component, for it to be registered in the form. It also gives you access to it’s properties and state of the input like errors and information whether it’s been clicked on by a user or not. React Final Form doesn’t provide any built-in validation, so you’ll have to pass on your own. You can pass multiple validations with:

<Field
name="email"
validate={composeValidators(required, email)}
>


Formik

<Formik
initialValues={{
firstName: '',
middleName: 'Paul',
lastName: '',
email: '',
description: '',
gender: 'male',
processing: false,
}}
onSubmit={onSubmit}
>
{({
errors,
touched,
handleSubmit,
isSubmitting,
values,
resetForm,
dirty,
}) => (
<Form onSubmit={handleSubmit}>
<div className="input-group">
<label htmlFor="firstName">First name</label>
<Field
placeholder="First name"
name="firstName"
type="text"
validate={required}
/>
</div>
{touched.firstName && errors.firstName && (<div className="error">{errors.firstName}</div>)}

With Formik, you have to wrap your form in the Formik component which again, gives you access to various props. What’s important here, you need to set initialValues object of each field in the form for it to be registered. Using Form component is optional because all it does is automatically hooking into Formik's handleSubmit and handleReset. Then you just use Field to create an input on the form (works the same with other inputs like radio and checkbox and for custom fields, you use component prop on the Field). Multiple validations is again as simple as passing: 

<Field
placeholder="Email"
name="email"
validate={composeValidators(required, email)}
/>

 

React Hook Form

const {
handleSubmit,
register,
errors,
getValues,
reset,
formState,
setError,
} = useForm({
nativeValidation: false,
defaultValues: {
middleName: 'Paul',
gender: 'male',
},
});

return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="input-group">
<label htmlFor="firstName">First name</label>
<input
type="text"
placeholder="First name"
name="firstName"
ref={register({ validate: required })}
/>
</div>
{errors.firstName && errors.firstName.message && <div className="error">{errors.firstName.message}</div>}

React Hook Form doesn’t use any wrapper components for its form. You get your props from the useForm hook. Fields are registered through the ref property on the input, using register and passing in your validation object. Custom components are not supported out of the box, so you’ll have to use another package from the author: react-hook-form-input. Hook Form ships with a number of built-in validations, like required, min, max, minLength, maxLength. Using validate let’s you use your own, custom ones, like for example, checking email format. You pass multiple validations with: 

<input
type="text"
placeholder="Email"
name="email"
ref={register({ validate: composeValidators(required, email) })}
/>

As a side note to validations, both React Final Form and Formik support both Field-level, as well as Form-level validations, through passing the validate prop to their respective form wrapper component. React Hook Form supports only Field-level validation.

<Form
validate={(values) => {
const errors = {};
if (!values.email) {
errors.email = Field is required;
} else if (!emailPattern.test(values.email)) {
errors.email = 'Invalid email;
}
//...
return errors;
};
}
>


Form submit, reset and async errors

React Final Form

<div className="input-group">
<button
type="submit"
disabled={submitting}
>
Submit
</button>
<button
type="button"
onClick={form.reset}
disabled={submitting || pristine}
>
Reset
</button>
</div>
{submitError && (<div className="error">{submitError}</div>)}
const onSubmit = async (values) => {
await sleep(500);

if (values.firstName === 'Larry') {
return { [FORM_ERROR]: 'Sorry, that name is taken.' };
}
const mes = `React Final Form
${JSON.stringify(values)}
`;

alert(mes);
};

To be honest there’s not much to explain here. You disable the buttons when form is being submitted, and also return a submit error with { [FORM_ERROR]: 'Your error' } which is imported from final-form. 

Formik

<div className="input-group">
<button
type="submit"
disabled={isSubmitting}
>
Submit
</button>
<button
type="button"
onClick={resetForm}
disabled={isSubmitting || !dirty}
>
Reset
</button>
</div>
{errors.submitError && <div className="error">{errors.submitError}</div>}const onSubmit = async (values, { setSubmitting, setErrors }) => {
await sleep(500);

if (values.firstName === 'Larry') {
setErrors({ submitError: 'Sorry, that name is taken' });

return;
}
const mes = `Formik
${JSON.stringify(values)}
`;

alert(mes);
setSubmitting(false);
};

In Formik you have to pass some additional arguments to the onSubmit function, namely setSubmitting to reset submitting state and setErrors to return an error.

React Hook Form

<div className="input-group">
<button
type="submit"
disabled={formState.isSubmitting}
>
Submit
</button>
<button
type="button"
onClick={reset}
disabled={formState.isSubmitting || !formState.dirty}
>
Reset
</button>
</div>
{errors.submitError && <div className="error">{ errors.submitError.message }</div> }const onSubmit = async (values) => {
await sleep(500);

if (values.firstName === 'Larry') {
setError('submitError', 'notMatch', 'Sorry, that name is taken');

return;
}
const mes = `React Hook Form
${JSON.stringify(values)}
`;

alert(mes);

React Hook Form is really similar to Formik in the way the submit and errors are handled. You simply call the setError function.

Which one to choose? 

While I’m not going to tell you the clear winner (because there isn’t any), I hope that I at least was able to highlight some of the differences between these libraries. I’m sure I haven’t addressed everything there is to address since that’s a really extensive subject, but I’ve tried to present the fundamentals of building a React form efficiently and painlessly.

Useful links: 

Code repository from the examples you'll find on Merixstudio GitHub

React Final Form: repository, npmbundle size and content

Formik: repositorynpm, bundle size and content

React Hook Form: repository, npmbundle size and contentform builder

Want to learn more insightful tips about React? Read about our Test-driven development approach! And if you would like to raise your skills within a great JS team - check our job offers


    
 

Navigate the changing IT landscape

Some highlighted content that we want to draw attention to to link to our other resources. It usually contains a link .