Forms are notoriously difficult to work with in React. Unlike other elements, form elements have their own internal state. In React, there are two distinct approaches for handling interactions with these elements.
Controlled vs. Uncontrolled Components
The first way is to allow inputs to behave like standard HTML inputs, managing their own value. Inputs behaving in this manner are referred to as uncontrolled components. The data from an uncontrolled input is accessible to the app when the form is submitted or via a ref. This approach is very performant, but typically it isn’t useful for custom components or situations where you need frequent access to values.
The alternative and often recommended way of interacting with forms is to use controlled components. A controlled input has its value managed by Javascript. Oftentimes, this is accomplished with something like a useState
hook, where the new value is set in the inputs onChange handler. Controlled inputs allow easier access to the input values, but it can be cumbersome to manage the onChange handlers and the state of a large form.
In light of the challenges presented by forms, it’s no surprise that many packages aim to make them easier to work with. Some of the more popular packages include:
Each of these packages is actively maintained at the time of writing this, and any of them can help make forms easier to incorporate in your app. The one you choose to use for your project should depend on your application and how your form fits into it.
Formik
Formik, the most popular library of the bunch, is a great choice if your form is relatively small or simple. It offers a straightforward API that allows quick development and easy access to the form values.
const MyForm = () => (
<Formik
initialValues={{
name: '',
email: '',
}}
onSubmit={()=>{}}
>
<Form>
<div>
<label htmlFor="name">Name </label>
<Field id="name" name="name" placeholder="Full name" />
</div>
<div>
<label htmlFor="email">Email </label>
<Field
id="email"
name="email"
placeholder="[email protected]"
type="email"
/>
</div>
<button type="submit">Submit</button>
</Form>
</Formik>
);
One important note about Formik is that all form data is stored in the Formik context. This works well for most use cases but does have some potential performance implications. In React, changes to a context provider’s value cause all consumers to rerender. In the case of Formik, all inputs are consumers of the Formik context. This essentially means that any change to an input will cause every input to re-render.
Sometimes it’s necessary to access your form values to perform conditional rendering of other inputs or components. In most cases, this means consuming the Formik context, which again, would cause the rerendering of that component and all of its children on any input. You can minimize the effects of this by being careful with the usage of the useFormikContext
hook.
If your form is very large, or if you plan to do a lot of conditional rendering, you may be better served by a different form library.
React Hook Form
React Hook Form is the only form library of the three mentioned here that leverages uncontrolled inputs. As a result of this decision, it shines in its ability to dramatically limit rerenders. It offers a number of useful hooks like:
- useForm – Create a form that inputs will register to
- useWatch – Watch the whole form or particular values within it
- useController – Provides support for controlled components
Unlike Formik, typing into an input does not affect other inputs within the form unless you’ve explicitly used the API to watch for updates.
function MyForm() {
const {
register,
handleSubmit,
} = useForm();
const onSubmit = (data) => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="name">Name </label>
<input placeholder="Full name" {...register("name")} />
</div>
<div>
<label htmlFor="email">Email </label>
<input placeholder="[email protected]" {...register("email")} />
</div>
<input type="submit" />
</form>
);
}
React Hook Form v7 has a bit larger API with several different hooks but provides a great deal of flexibility. Its register function provides a clean, convenient way of integrating into your app’s existing inputs. If you have a larger form and need to be able to watch form values, this library is a great option.
React Final Form
React Final Form is a wrapper around Final Form, developed by the creator of Redux Forms. It aims to address a couple of issues with its predecessor, namely its coupling to both Redux and React, as well as complaints of unnecessary rerendering.
Similar to React Hook Form, it offers an API that allows your components to subscribe to form updates and uses a subscription model to do so. Inputs within React Final Form are largely expected to be children of the <Field/>
component. That said, the API also exposes a useField hook that is very similar to the useController hook of React Hook Form.
const MyForm = (props) => (
<Form
onSubmit={props.onSubmit}
render={({ handleSubmit }) => (
<form onSubmit={handleSubmit}>
<div>
<label>Name</label>
<Field
name="name"
component="input"
type="text"
placeholder="Full Name"
/>
</div>
<div>
<label>Email</label>
<Field
name="email"
component="input"
type="email"
placeholder="[email protected]"
/>
</div>
<input type="submit" />
</form>
)}
/>
);
My Choice: React Hook Forms
All three options will help you get a form into your app without much fuss. If I was asked to build a form today, I’d suggest React Hook Forms first. Its flexibility, performance, and active contributors make it hard to overlook.