Forms are used to collect data for processing from users. Many websites today have one or more forms. If you work with React, you know that it provides a way to handle forms using controlled components. However, it can become tedious with a lot of repetitive code if you build a lot of forms, and you may want to also validate and keep track of the visited fields or form state. For this reason, you might seek out a form library that can help make it easier to build forms of varying complexity, with validation and state management.
In this post, I'm going to list some React form libraries you should consider. I'll include code snippets for a form to collect data and you will see the syntax for each and how each one differs in usage.
Formik
Formik is one of the popular libraries (with 26.2k stars on GitHub) for building forms in React. Formik helps you with managing the form state, handling submission, formatting and validating form values. It's also quite small in size. It is 13.1 kB when gzipped and minified, with support for TypeScript and works with React Native.
Here's how you would write a form to collect user data with Formik:
import { Formik, Form, Field, ErrorMessage } from "formik";
const DataForm = () => (
<>
<h1>Your Data</h1>
<Formik
initialValues={{ name: "", email: "", acceptedTerms: false }}
validate={(values) => {
const errors = {};
if (!values.name) {
errors.name = "Required";
}
if (!values.acceptedTerms) {
errors.acceptedTerms =
"You must accept the terms and conditions before you proceed.";
}
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 }) => {
// post data to server
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}}
>
{({ isSubmitting, dirty, handleReset }) => (
<Form>
<div>
<label>
Name
<Field type="text" name="name" />
</label>
<ErrorMessage name="name" component="span" />
</div>
<div>
<label htmlFor="email">Email</label>
<Field type="email" name="email" />
<ErrorMessage name="email" component="span" />
</div>
<div>
<label>Accept terms</label>
<Field type="checkbox" name="acceptedTerms" />
<ErrorMessage name="acceptedTerms" component="span" />
</div>
<button
type="button"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</button>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
</Form>
)}
</Formik>
</>
);
export default DataForm;
Formik comes with components that make it easier to manage form state and then exposes the form data via props. You wrap the form with the <Formik />
component and pass it props. In the example, I passed in prop for initialValues
, which is an object with keys that match the name
or id
of the fields it should bind to and the values for the fields when they’re rendered.
The onSubmit
prop is the function that will be called when the form is submitting and the form values are valid. If the form is invalid, then the error messages will be displayed for each field using the <ErrorMessage />
component. I prefer using <ErrorMessage />
compared to checking the error state and if the field has been visited. That is, writing <ErrorMessage name="email" component="span" />
instead of {errors.email && touched.email && <span>errors.email</span>}
.
You can use field-level validation or form-level validation by specifying a validate
props for <Field />
or <Formik />
. You specify a synchronous or asynchronous function that returns the error message for field-level validation, or an object with keys that match the respective fields for form-level validation. You can use libraries like Yup or Joi if you don't want to write your own validation function. Formik has a special prop for Yup called validationSchema
which will automatically transform Yup's validation errors into a pretty object whose keys match the respective form fields.
You can access the form state through props such as dirty
and isSubmitting
, as seen in the example, and also event handlers like handleSubmit
. In the example, the form is reset by calling the handleReset
function passed in as props.
I like how easy it can be to use the <Field />
and <ErrorMessage />
, but you can also use HTML form controls or pass a custom component to <Field />
. You also have access to the event handlers, form values, and validation status as props.
KendoReact Form
KendoReact Form is a small and fast library with full accessibility support, all just in 6.2 kB gzipped and minified. It is the smallest in size, when compared to the others on this list. It has a simple syntax and provides components and props to access form state, with full support for TypeScript. It supports field-level and form-level validation. Let's look at a similar user data form built with KendoReact.
import { useCallback } from "react";
import { Form, Field, FormElement } from "@progress/kendo-react-form";
const emailRegex = new RegExp(/\S+@\S+\.\S+/);
const emailValidator = (value) =>
emailRegex.test(value) ? "" : "Please enter a valid email.";
const CustomCheckbox = (fieldRenderProps) => {
const {
validationMessage,
visited,
value,
onChange,
onFocus,
onBlur,
...props
} = fieldRenderProps;
const onValueChange = useCallback(() => {
onChange({ value: !value });
}, [onChange, value]);
return (
<div onFocus={onFocus} onBlur={onBlur}>
<label htmlFor={props.name}>{props.label}</label>
<input
type="checkbox"
onChange={onValueChange}
checked={value}
id={props.id}
/>
{visited && validationMessage && <span>{validationMessage}</span>}
</div>
);
};
const checkboxValidator = (value) =>
value ? "" : "You must accept the terms and conditions before you proceed.";
const DataForm = () => {
const handleSubmit = (dataItem) => alert(JSON.stringify(dataItem, null, 2));
return (
<Form
initialValues={{ name: "", email: "" }}
onSubmit={handleSubmit}
validator={({ name, email, acceptedTerms }) => ({
name: name ? "" : "Your name is required",
email: emailValidator(email),
acceptedTerms: checkboxValidator(acceptedTerms),
})}
render={(formRenderProps) => (
<FormElement>
<fieldset>
<legend>Your Data</legend>
<div>
<label>Full Name </label>
<Field name="name" component="input" />
{formRenderProps.touched && formRenderProps.errors.name && (
<span>{formRenderProps.errors.name}</span>
)}
</div>
<div>
<label>Email </label>
<Field name="email" type="email" component="input" />
{formRenderProps.touched && formRenderProps.errors.email && (
<span>{formRenderProps.errors.email}</span>
)}
</div>
<Field
name="acceptedTerms"
label={"Accept Terms"}
component={CustomCheckbox}
/>
</fieldset>
<div>
<button
type="submit"
disabled={!formRenderProps.modified}
onClick={formRenderProps.onFormReset}
>
Reset
</button>
<button type={"submit"} disabled={!formRenderProps.allowSubmit}>
Submit
</button>
</div>
</FormElement>
)}
/>
);
};
export default DataForm;
The syntax is simple to get started with. You pass some props to the <Form />
component. In the example, I set the initialValues
, onSubmit
prop to handle the form submission, and validator
for form-level validation. If you choose to use field-level validation, you can pass validator
props to <Field />
. The <Field />
component uses the name prop to store the value for the input and can render a custom component or an HTML element such as input
. Unlike Formik where you can specify the type
prop and omit component
and it'll render <input />
, KendoReact requires you to pass a value for component
.
You get access to the form state and event handlers as props. In the example, I used onFormReset
to reset the form when the Reset button is clicked, and allowSubmit
to know when to disable the Submit button. You also get access to the errors
object, which contains the message for each field that failed validation.
Usage of KendoReact Form requires a paid license. Getting a KendoReact license gives you access to a suite of components to build rich, accessible web applications, with fast technical support. You get different components for displaying data (e.g. Grid), form input components like checkbox, dropdown, and different data input components.
The Label package gives you components that can be visually associated with form elements, and assistive technologies will read the label's content when the form element is focused. The form input element will receive focus when the label associated with it is clicked. For example, the Error component, which is part of the Labels package, allows you to display an error message. Here's an example of how you can use the KendoReact Input, Label, and Form packages together.
import { useState } from "react";
import { Label, Error } from "@progress/kendo-react-labels";
import { Input } from "@progress/kendo-react-inputs";
import { FormElement } from "@progress/kendo-react-form";
const App = () => {
const [value, setValue] = useState();
const editorId = "firstName";
return (
<FormElement style={{ maxWidth: 400 }}>
<Label editorId={editorId}>First Name: </Label>
<Input
id={editorId}
value={value}
ariaDescribedBy={"firstNameError"}
onChange={(e) => setValue(e.value)}
/>
{!value && <Error id={"firstNameError"}>This field is required.</Error>}
</FormElement>
);
};
The KendoReact Form documentation is well detailed and includes form design guidelines on creating and styling forms with accessibility support.
React Hook Form
React Hook Form is a flexible library that embraces the hooks API and uncontrolled components. It is open source and has 17.3k GitHub stars, and it’s 9.1kB when gzipped and minified.
The API is slightly different from the others I mentioned. It has TypeScript and React Native support, but unlike the others I mentioned, there's no component to wrap your form. You will use the useForm
hook it provides to access form state. Let's look at an example.
import { useForm } from "react-hook-form";
export default function App() {
const { register, handleSubmit, errors, reset, formState } = useForm();
const { isDirty, isSubmitting } = formState;
const onSubmit = (data) => alert(JSON.stringify(data, null, 2));
return (
<form onSubmit={handleSubmit(onSubmit)}>
<h1> Your Data</h1>
<div>
<label>Name</label>
<input
type="text"
placeholder="Full Name"
name="name"
ref={register({ required: "Name Required " })}
/>
<span>{errors.name?.message}</span>
</div>
<div>
<label>Email</label>
<input
type="text"
placeholder="Email"
name="email"
ref={register({
required: "Email Required",
pattern: { value: /^\S+@\S+$/i, message: "Invalid email address" },
})}
/>
<span>{errors.email?.message}</span>
</div>
<div>
<label>Accept Terms</label>
<input
type="checkbox"
placeholder="Accept Terms"
name="acceptedTerms"
ref={register({ required: true })}
/>
{errors.acceptedTerms && <span>You must accepet the terms</span>}
</div>
<button type="button" onClick={reset} disabled={!isDirty || isSubmitting}>
Reset
</button>
<input type="submit" disabled={isSubmitting} />
</form>
);
}
To use this library, you call the useForm()
hook which will return objects and functions to manage form state. The handleSubmit
function will be called when the form is submitting. It accepts two functions as arguments: the first one will be called with the form data if the form validation is successful, and the second one will be called when the validation fails.
The register
function allows you to register an input/select element Ref and supply validation rules as well. You can specify the error message for a validation rule when it is defined or skip it. You can see the different approach in the rule applied to the email
and acceptedTerms
input. If you specify an error message, you can access it in the errors object, through the message
property of the validated field. If you would like to use a component to render the error message like you saw with Formik, you can install the @hookform/error-message
package. With it, you can use it to display the error message for name and email as follows:
import { ErrorMessage } from "@hookform/error-message";
// other necessary code ...
<ErrorMessage errors={errors} name="name" />
<ErrorMessage
errors={errors}
name="email"
render={({ message }) => <p>{message}</p>}
/>
React Final Form
React Final Form is a subscription-based form state management library based on Final Form. It uses the Observer pattern so that only the components that need updating are re-rendered as the form's state changes. By default, it subscribes to all changes, but if you want to optimize for blazing-fast perfection, you may specify only the form state that you care about.
Let's look at the syntax for using Final Form.
import { Form, Field } from "react-final-form";
const DataForm = () => (
<>
<h1>Your Data</h1>
<Form
onSubmit={(values) => alert(JSON.stringify(values, 0, 2))}
initialValues={{ acceptedTerms: true }}
validate={(values) => {
const errors = {};
if (!values.name) {
errors.name = "Required";
}
if (!values.acceptedTerms) {
errors.acceptedTerms =
"You must accept the terms and conditions before you proceed.";
}
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;
}}
render={({
handleSubmit,
form,
submitting,
pristine,
values,
errors,
touched,
}) => (
<form onSubmit={handleSubmit}>
<Field name="name">
{({ input, meta }) => (
<div>
<label>Username</label>
<input {...input} type="text" placeholder="Username" />
{meta.error && meta.touched && <span>{meta.error}</span>}
</div>
)}
</Field>
<div>
<label>Twitter Handle</label>
<Field name="twitter" component="input" type="text" />
</div>
<Field name="email">
{({ input, meta }) => (
<div>
<label>Email</label>
<input {...input} type="email" />
{meta.error && meta.touched && <span>{meta.error}</span>}
</div>
)}
</Field>
<div>
<label>Accept Terms</label>
<Field name="acceptedTerms" component="input" type="checkbox" />
{touched.acceptedTerms && errors.acceptedTerms && (
<span>{errors.acceptedTerms}</span>
)}
</div>
<div>
<button
type="button"
onClick={form.reset}
disabled={submitting || pristine}
>
Reset
</button>
<button type="submit" disabled={submitting}>
Submit
</button>
</div>
</form>
)}
/>
</>
);
export default DataForm;
The two components from React Final Form used in the example are <Form />
and <Field />
. The <Form />
component is a wrapper over the HTML form and it manages the form state and events. You can set initial values to use for initialising the form state, the submit handler, and validate
prop for form-level validation. You can also do field-level validation by passing a validate
props to the <Field />
component.
You get access to render props like values
which is the form data, handleSubmit
, touched
, and errors
. The <Field />
component registers a field with the form, subscribes to the field state, and injects both field state and callback functions (onBlur, onChange, and onFocus) via render prop. I used a child render function to render a label with an associated input and error message for the name and email field.
Unlike Formik and React Hook Form, it doesn't have an <ErrorMessage />
component. However, you can easily build one that can be reused in your project using the useField
hook.
import { useField } from "react-final-form";
const ErrorMessage = ({ name }) => {
const {
meta: { error, touched },
} = useField(name, { subscription: { error: true, touched: true } });
return error && touched ? <span>{error}</span> : null;
};
React Final Form is primarily maintained by Erik Rasmussen, who also built Redux Form. React Final Form is an evolution of the lessons he learnt while using and maintaining Redux Form, and also the feedback from the community. It is open source and has 6.3k GitHub stars, weighing 3.2 kB when gzipped and modified, plus 5.4 kB gzipped for Final Form.
Conclusion
Any of the React form libraries listed is fast and helps you build and manage complex forms that are performant. Formik, KendoReact Form, and React Final Form provide components for you to work with, while React Hook Form uses hooks and uncontrolled inputs. I am not in favour of the style/syntax used in React Hook Form but if you like that approach, then use it.
I would rather go for React Final Form or KendoReact Form. I like the syntax better and I can also build a <ErrorMessage />
component if needed. For me, it requires less code while working with React Final Form compared to the others.
For KendoReact Form, the only downside I think there is that it's not free, unlike the others. However, getting a license for the whole KendoReact library gives you access to a lot of cool components to build a rich, performant, and accessible application. There's a nice theme/styling that you can do with it and all your components have the same look and feel. You get different input components and they all work nicely together with the Form package for accessible React forms. All that is well documented in their Form Guidelines, which is also a useful read even if you're not using KendoReact Forms.
Like I said, any of these libraries is a good choice if it fits your needs.
Top comments (19)
I usually use React Hook Forms, they are updating the library often and with the context it is very easy to create components that take advantage of the form utilities they provide.
If the form is big enough the combination of React Hook Forms + Yup is awesome, I used to really hate working with forms but now I can admit it's very affordable.
The only downside of it is its asynchronicity. Whenever you need to get the most actual data you need to use
useWatch
orwatch
fromuseForm
hook. They decrease performance heavily.Also, It doesn't work properly in case of setting the default value.
I haven't been in such situation before.
But when you use this with material UI. you will get to know how complex is this to handle the default value.
You can also have a look into this.
npmjs.com/package/formify-react
This is just a share of a product I use that has a deep integrations with React, Vue and even Angular. Have you heard of Wijmo? grapecity.com/wijmo/react-ui-compo... It has 100+ dynamic JS UI Components.
Low level learning curve available on npm
You shouldn't be doing this. Your comment is not related to my post and trying to promote your product in a manner I find to be unethical.
Didn't mean to offend, we have a react library similar to Kendo. It relates to your article as we are not listed and thought I would send you the details, in case you were interesting in learning about other alternatives. I will remove my comment but it was not be unethical, it was being informative. This is a place where we come together to learn and share. I learned from your article and then I shared what I know. Sorry Peter.
Seems you updated your comment....
How does Wijmo deal with Forms in React? I couldn't find anything relating to that in your library.
Hey @pmbanugo ! What about uniforms.tools/? IMHO it's a good one. What made you decide it wasn't in your summary? I think it has even better solutions in some specific areas than mentioned ones.
Can you suggest a form library for a multi step form in which I can include interactivity ( such as handleClick or onHover ) for the step numbers (the numbers at the top that show the step you are in) . I want to include handle click such that user goes directly to that step if he clicks on a certain number (step number).
It's mostly the combination of forms with a tabstrip, customizing the tab strip... as to what that takes, depends on your UI components you're already using as to what approach or styling is needed.
In the end, what you're doing is a combination of things, not an out of the box component. This is where you get into custom development beyond CRUD.
Hi Gupta,
Any of the libraries I mentioned can do that. Here's an example using KendoReact .
Thanks
You can also have a look into this.
github.com/react-component/field-form
I prefer to just use forms provided by bootstrap:
react-bootstrap.github.io/componen...
Nice article. You said "while React Hook Form uses hooks and uncontrolled inputs". I'm not sure that you meant that, but React Hook Form works pretty well with controlled inputs also (done it myself).
I haven't looked at it recently but I think that was the case as at when I wrote the article
Some comments have been hidden by the post's author - find out more