Editor’s note: This article was updated on 23 March 2022 to include updated information about the below listed React form validation solutions.
As a developer, it’s usually best to not reinvent the wheel. That’s why the first step for implementing a solution is to look for existing solutions. And it’s what React is all about — creating reusable components so we don’t end up repeating ourselves.
In this article, we’ll take a look at some of the most popular solutions for form management and validation in React:
Let’s get started!
A roundup on React form solutions wouldn’t be complete without Formik. With 30k stars on GitHub, it is basically the most popular library for building and managing forms in React. Formik is a scalable, performant, form helper with a minimal API. It handles form state management, validation, and error handling.
Installation
yarn add formik
Basic usage
Here’s how to use Formik in its most basic form. The useFormik
Hook is where all the goodness is encapsulated. To use it, it only requires you to pass in the initialValues
which is an object containing the default values of each of your form fields. And the onSubmit
handler that gets called when the form validation passes.
When useFormik
is called, it returns the event handlers and form state which we could use to specify when to trigger form validation. The validation rules are added through the validate
property. A callback function is passed as the value for this property. And it’s where you validate the individual form fields. The validation gets triggered on the onChange
event of every field. This means that by default when the user updates the value for the first field, all the other fields in the form will have their validation code triggered as well.
We don’t really want that, that’s why we need to extract the handleBlur
and touched
from the return object of useFormik
. The former allows us to listen for blur events from the fields, while the latter allows us to check whether the user has already visited the field. From there, we can just check for the errors through the errors
:
import React from "react"; import { useFormik } from "formik"; function FormikComponent() { function validate(values) { const errors = {}; if (!values.favoriteFood) { errors.favoriteFood = "Required"; } if (!values.favoritePlace) { errors.favoritePlace = "Required"; } return errors; } const { handleSubmit, handleChange, handleBlur, touched, values, // use this if you want controlled components errors, } = useFormik({ initialValues: { favoriteFood: "", favoritePlace: "", }, validate, onSubmit: (values) => { console.log(JSON.stringify(values)); // values = {"favoriteFood":"ramen","favoritePlace":"mountains"} }, }); return ( <form onSubmit={handleSubmit}> <label htmlFor="favoriteFood">Favorite Food:</label> <input type="text" name="favoriteFood" onChange={handleChange} onBlur={handleBlur} /> {touched.favoriteFood && errors.favoriteFood ? <div>errors.favoriteFood</div> : null} <label htmlFor="favoritePlace">Favorite place:<label> <input type="text" name="favoritePlace" onChange={handleChange} onBlur={handleBlur} /> {touched.favoritePlace && errors.favoritePlace ? <div>errors.favoritePlace</div> : null} <button type="submit">submit</button> </form> ); } export default App;
The above example is nice, but it can get out of hand quickly on more complex forms. For that, we could use the Yup library. Yup is a schema builder for parsing values and validating them. You can install it with the following command:
yarn add yup
To use Yup, all you have to do is replace validate
with validationSchema
. This is specifically created for Yup integration:
import * as Yup from "yup"; function App() { const schema = Yup.object({ favoriteFood: Yup.string().required("Required"), favoritePlace: Yup.string().required("Required"), }); const { ... } = useFormik = ({ // ... validationSchema: schema // replace validate with this }); }
Summary
React Final Form is a framework-agnostic form validation library with zero dependencies. Final Form is the name of the actual library, while React Final Form is the React wrapper. It is subscription-based, so only the specific form fields will get updated when the form state is updated.
Installation
yarn add final-form react-final-form
Basic usage
There are two ways of validating forms with React Final Form: record-level and field-level. Record level is pretty much the same as how it’s done with Formik. And just like Formik, you can also easily use Yup for implementing validation rules. The way you validate your form depends on how complex your form is. If your form is fairly simple, you can stick with field-level validation. Otherwise, use record-level.
Here’s an example of record-level validation using Yup. This requires us to use the setIn
utility function from the Final Form library to transform the default error object into an object which uses dot-and-bracket syntax (e.g. people[0].name.first
). In the code below, you can see that the way it works is similar to Formik. Though it relies on the use of custom components (<Form>
and <Field>
to wrap the primitive HTML elements to provide it with state and input callbacks:
import React from "react"; import { setIn } from "final-form"; import { Form, Field } from "react-final-form"; import * as Yup from "yup"; const schema = Yup.object({ favoriteFood: Yup.string().required("Required"), }); const validate = async (values) => { try { await schema.validate(values, { abortEarly: false }); } catch (err) { const errors = err.inner.reduce((formError, innerError) => { return setIn(formError, innerError.path, innerError.message); }, {}); return errors; } }; const onSubmit = (values) => { console.log("values: ", values); }; function FinalFormComponent() { return ( <Form onSubmit={onSubmit} validate={validate} render={({ handleSubmit, form, submitting, pristine, values }) => ( <form onSubmit={handleSubmit}> <Field name="favoriteFood"> {({ input, meta }) => ( <div> <label>Favorite Food</label> <input {...input} type="text" placeholder="favoriteFood" /> {meta.error && meta.touched && <span>{meta.error}</span>} </div> )} </Field> <button type="submit">submit</button> </form> )} /> ); }
And here’s how it’s done using field-level validation:
import React from "react"; import { Form, Field } from "react-final-form"; const onSubmit = (values) => { console.log("values: ", values); }; const required = (value) => (value ? undefined : "Required"); function FinalFormComponent() { return ( <Form onSubmit={onSubmit} render={({ handleSubmit, form, submitting, pristine, values }) => ( <form onSubmit={handleSubmit}> <div> <Field name="favoriteFood" validate={required}> {({ input, meta }) => ( <div> <label>Favorite Food</label> <input {...input} type="text" placeholder="Favorite food" /> {meta.error && meta.touched && <div>{meta.error}</div>} </div> )} </Field> </div> <button type="submit">submit</button> </form> )} /> ); } export default FinalFormComponent;
Summary
Unform is a performance-focused form library for React. It does so by using uncontrolled form components.
Installation
yarn add @unform/core @unform/web yup
Basic usage
Unform requires you to create your own custom component where you use the useField
Hook to access the field data and methods for interacting with the field. In the code below, we’re only extracting error
and clearError
so we can display and clear the validation error message:
import React, { useRef } from "react"; import { useField } from "@unform/core"; export default function Input({ name, ...inputProps }) { const inputRef = useRef(null); const { error, clearError } = useField(name); return ( <> <input ref={inputRef} onFocus={clearError} {...inputProps} /> {error && <span>{error}</span>} </> ); }
Once you have a custom component, you can now include it in your form. Unform uses a Form
component as a container for form fields. This is where you add the ref
and onSubmit
handler. In the example below, we’re also using Yup for validation. Unform doesn’t include its own validation library so you can use whichever you want for validating your forms:
import React, { useRef } from "react"; import * as Yup from "yup"; import { Form } from "@unform/web"; import Input from "./components/Input"; function UnformComponent() { const formRef = useRef(null); async function handleSubmit(data) { try { formRef.current.setErrors({}); const schema = Yup.object().shape({ email: Yup.string().email().required() }); await schema.validate(data, { abortEarly: false, }); console.log(data); } catch (err) { const validationErrors = {}; if (err instanceof Yup.ValidationError) { err.inner.forEach((error) => { validationErrors[error.path] = error.message; }); formRef.current.setErrors(validationErrors); } } } return ( <Form ref={formRef} onSubmit={handleSubmit}> <Input name="email" type="email" /> <button type="submit">submit</button> </Form> ); } export default UnformComponent;
Note: If you’re planning to explore this library further, be sure to only follow tutorials that use version 2. Better yet, just stick to the official docs. This is because some tutorials use version 1. You can quickly determine that it’s using version 1 because it uses custom form components. Version 2 embraces the use of primitive HTML elements. Check out the migration guide for more information.
Summary
@unform/mobile
packageuseField
APIReact Form provides Hooks for managing form state and validating forms in React.
Installation
yarn add react-form
Basic usage
The way you use React Form is similar to how you use Unform so you first have to create a custom field component that uses the useField
Hook. This provides you with properties that store the field errors. Oftentimes, you’ll only want to display errors once the user has already “touched” the specific field. This is what the isTouched
property allows you to check.
To validate the field, you pass an object containing the validation function:
import React from "react"; import { useField } from "react-form"; function Field({ name }) { const { value = "", meta: { error, isTouched }, getInputProps, } = useField(name, { validate: (value) => { if (!value) { return `${name} is required.`; } return false; }, }); return ( <> <input type="text" {...getInputProps()} value={value} /> {isTouched && error ? <span>{error}</span> : null} </> ); } export default Field;
Once you have a custom field component, you can now use the useForm
Hook to validate your form.
Note the Form
component is extracted from useForm
:
import React from "react"; import { useForm } from "react-form"; import Field from "./components/Field"; function ReactFormComponent() { const { Form, meta: { isSubmitting }, } = useForm({ onSubmit: async (values, instance) => { console.log("submit: ", values); }, }); return ( <Form> <label> Favorite Food: <Field name={"favoriteFood"} /> </label> <div> <button type="submit">Submit</button> </div> </Form> ); } export default ReactFormComponent;
Summary
useForm
A simple React component that allows you to build and validate HTML forms using JSON schema. It ships with simple form validation by default. Validation rules such as required
and minLength
are built into it. If you want to use more advanced validation rules, you’ll have to write them on your own.
It currently supports Bootstrap and Material UI for HTML form semantics. But you can use your own HTML form template as well.
This library is very flexible when it comes to building forms. The only disadvantage is that there’s a bit of a learning curve needed to fully use it because you’re essentially using an API to build forms.
Installation
There are two built-in themes that you can use with this library. By default, there’s Bootstrap:
yarn add @rjsf/core
But if you want to use Material UI, install the following instead:
yarn add @rjsf/material-ui
Basic usage
Using the library is a three-step process:
When you specify your schema, you can add the data type, label, and optionally a custom format that the user input needs to follow. If any of your fields have a custom format, you also need to supply the customFormats
prop to the Form
component. By default, the form will only be validated when the user submits. But you can also set the liveValidate
prop to true
so that it will validate the fields as the user inputs data. For custom validation, you can supply the validate
prop. This is the validation function to be used for validating specific fields in your form. Lastly, the onSubmit
function will only get executed once all the fields have passed validation:
import React, { Component } from "react"; import { render } from "react-dom"; import Form from "@rjsf/core"; const schema = { type: "object", required: ["email", "age"], properties: { email: { type: "string", email: "Email Address", format: "email_address" }, age: { type: "number" }, done: { type: "boolean", title: "Done?", default: false }, }, }; const customFormats = { email_address: /\S+@\S+\.\S+/, }; function validate(formData, errors) { if (formData.age < 18) { errors.age.addError("You should be 18 years old or older."); } return errors; } const onSubmit = ({ formData }, e) => console.log("submit: ", formData); function ReactJsonSchemaComponent() { return ( <Form schema={schema} onSubmit={onSubmit} liveValidate={true} noHtml5Validate={true} showErrorList={false} customFormats={customFormats} validate={validate} /> ); } export default ReactJsonSchemaComponent;
Summary
React Hook Form is a lightweight React form validation library that mainly uses Hooks to add form validation to HTML input elements. Choose this library if you’re looking for a modern form validation library that’s very performant and easy to use.
Out of all the libraries mentioned in this post. React Hook Form requires the least amount of code that must be written in order to use it. It’s only a two-step process: use the Hook then add a ref to your fields.
Another great thing about this library is that it has very good documentation with lots of examples. You can find guides on almost any use case you can think of such as integrating with an existing form, or how to use it with UI libraries like Material UI.
Installation
yarn add react-hook-form
Basic usage
The useForm
Hook provides all the data and methods required to implement form validation. You need to pass the name of the field as a mandatory argument to the register()
method so that the field can be uniquely identified in the form. Next, invoke and destructure the register()
method on the input component. This is what registers the uncontrolled component into the Hook. It requires you to pass the validation rules you need. Optionally, you can also pass the error message you want to show via the message
property.
In the example below, we used the required
and pattern
rule to validate an email field:
import React from 'react'; import { useForm } from "react-hook-form"; function ReactHookFormComponent() { const { register, handleSubmit, formState: {errors} } = useForm(); const onSubmit = data => console.log(data); // {"email":"[email protected]"} return ( <form onSubmit={handleSubmit(onSubmit)}> <input {...register("email", { required: true, pattern: /\S+@\S+\.\S+/ })} /> {errors.email?.type === 'required' && <span> This field is required</span>} {errors.email?.type === 'pattern' && <span> Invalid email</span>} <button type="submit">submit</button> </form> ); } export default ReactHookFormComponent;
Summary
required
, min
, max
, pattern
, and othersTextInput
)Redux Form allows you to manage your form state (form input values, form errors, etc.) by means of Redux. Redux Form uses the store for keeping track of form state, thus the main disadvantage of this library is its performance. Even if you’re already using Redux in your application, only use it if you need tight coupling between your existing Redux state and your form data.
Installation
yarn add redux-form redux react-redux
Basic usage
To use Redux Form, you need to extract the Field
component and the reduxForm
higher-order component from redux-form
. At a minimum configuration, all you need is to pass a unique name for the form (in this case it’s bio
). But here, we’re also passing in a validation function. This gets executed when the user submits the form.
To create an input field, you use the Field
component. Here, we pass in the type
, name
, component
to be used for rendering, and the label
. The renderField()
function is responsible for returning the input component. Aside from rendering the label and input, it also renders the error messages. This is made possible by Redux Form’s Field
component:
import React from 'react'; import { Field, reduxForm } from 'redux-form'; const validate = values => { const errors = {} if (!values.name) { errors.name = 'Required'; } if (!values.email) { errors.email = 'Required'; } else if (!/\S+@\S+\.\S+/i.test(values.email)) { errors.email = 'Invalid email address' } return errors; } const renderField = ({ input, label, type, meta: { touched, error } }) => ( <div> <label>{label}</label> <div> <input {...input} placeholder={label} type={type} /> {touched && (error && <span>{error}</span>)} </div> </div> ); let BioForm = props => { const { handleSubmit } = props; return ( <form onSubmit={handleSubmit}> <div> <Field type="text" name="name" component={renderField} label="Name" /> </div> <div> <Field type="email" name="email" component={renderField} label="Email" /> </div> <button type="submit">Submit</button> </form> ); } BioForm = reduxForm({ form: 'bio', validate })(BioForm); export default BioForm;
To bring it all together, include the formReducer
to your existing reducers then pass an onSubmit
handler to your form component:
import React, { Component } from 'react'; import { createStore, combineReducers } from 'redux'; import { reducer as formReducer } from 'redux-form'; import { Provider } from 'react-redux'; import BioForm from './components/BioForm'; const rootReducer = combineReducers({ // your existing reducers.. form: formReducer }); const store = createStore(rootReducer); class ReduxFormComponent extends Component { submit = values => { console.log('submit: ', values); } render() { return ( <Provider store={store}> <BioForm onSubmit={this.submit} /> </Provider> ); } } export default ReduxFormComponent;
Summary
Formsy is a form input builder and validator for React. It uses simple syntax for form validation rules. It also includes handlers for different form states such as onSubmit
and isValid
. Formsy is a good choice if you’re looking for an all-in-one solution for building and managing forms. The biggest downside of this library is that it doesn’t support React Hooks.
Installation
yarn add formsy-react
Basic usage
Formsy is similar to Unform in that it requires you to create your own custom component wrapped with the withFormsy
higher-order component. This provides all the properties and methods required to manipulate the input component and conditionally display error messages:
import React, { Component } from 'react'; import { withFormsy } from 'formsy-react'; class FormsyInput extends Component { constructor(props) { super(props); this.changeValue = this.changeValue.bind(this); } changeValue(event) { this.props.setValue(event.currentTarget.value); } render() { const errorMessage = this.props.errorMessage; return ( <div> <input onChange={this.changeValue} type="text" value={this.props.value || ''} /> <span>{!this.props.isPristine ? errorMessage : ''}</span> </div> ); } } export default withFormsy(FormsyInput);
Once you’ve created a custom component, you can now use it in your forms. To create a form, you should use the Formsy
component. This requires the onValidSubmit
prop. This only gets executed if all the form fields have a valid input. In the below example, we used the isEmail
validator, but there are many others you can use:
import React, { Component } from 'react'; import Formsy from 'formsy-react'; import FormsyInput from './components/FormsyInput'; export default class App extends Component { submit(model) { console.log('valid form: ', model); // {email: "[email protected]"} } render() { return ( <Formsy onValidSubmit={this.submit}> <FormsyInput name="email" validations="isEmail" validationError="This is not a valid email" required /> <button type="submit"> Submit </button> </Formsy> ); } }
Summary
isEmail
andisUrl
Simple React Validator is a Laravel-inspired React form validation library. Out of all the libraries reviewed in this article, this one has the biggest collection of built-in validation rules.
Installation
yarn add simple-react-validator
Basic usage
The example in the GitHub repo doesn’t really use Hooks, so you have to dig a bit deeper to find out how to use it with Hooks. In the code below, you can see that it mainly uses controlled components so you have to manage the form state independently. Another downside of this library is that you have to use forceUpdate
every time you need to show the error messages:
import React, { useState, useRef } from "react"; import SimpleReactValidator from "simple-react-validator"; function SimpleReactValidatorComponent() { const [email, setEmail] = useState(""); const simpleValidator = useRef(new SimpleReactValidator()); const [, forceUpdate] = useState(); const form = React.createRef(); const submitForm = () => { const formValid = simpleValidator.current.allValid(); if (!formValid) { simpleValidator.current.showMessages(true); forceUpdate(1) } else { console.log('submit form.'); } }; return ( <form ref={form}> <label>Email</label> <input name="email" type="text" value={email} onChange={(e) => setEmail(e.target.value)} onBlur={() => { simpleValidator.current.showMessageFor("email") forceUpdate(1); }} /> {simpleValidator.current.message("email", email, "required|email")} <button type="button" onClick={submitForm}>submit</button> </form> ); } export default SimpleReactValidatorComponent;
Summary
rc-field-form
is a performant form component library that has strong TypeScript support. The documentation does not explain the this library’s usage in detail. You’ll have to understand the use cases from the examples.
Installation
yarn add rc-field-form
Basic usage
The <Form />
component is used to wrap the form input fields. When the form is successfully submitted, the onFinish
prop triggers the callback function with the form values. Similarly, when there’s an error in the form the onFinishFailed
prop calls a callback with the error values.
You can wrap your custom <Input />
component with the <Field />
component and pass the name
and the validation rules for the input field. The Form.useForm()
hook can be used to access errors and values in the form.
import Form, { Field } from "rc-field-form"; const Input = ({ value = "", ...props }) => <input value={value} {...props} />; const Demo = () => { const [form] = Form.useForm(); const onSubmit = (v) => { console.log(v); }; const onError = (errors) => { console.log(errors); }; const renderFieldErrors = (errors) => errors.map((err) => <p key={err}>{err}</p>); return ( <Form form={form} onFinish={onSubmit} onFinishFailed={onError}> {() => { const emailErrors = form.getFieldError("email"); return ( <> <Field name="email" rules={[{ required: true, type: "email" }]}> <Input placeholder="Email" /> </Field> {renderFieldErrors(emailErrors)} <div> <button>Submit</button> </div> </> ); }} </Form> ); };
Summary
If you’re starting a new project or are simply looking for a modern and actively maintained library, it’s best to avoid the following libraries. These libraries are either no longer being actively maintained or use an outdated React API:
Here’s a list of other form libraries to explore. These mostly do the same things as the libraries we’ve gone through so I’ll just leave you to explore them for yourself:
In this article, we’ve taken a look at some of the most popular and interesting React form validation libraries out there. As you have seen, most of them have a similar intuitive API for managing forms and validation errors. Each one has its own pros and cons based on your needs. But for most cases, you won’t go wrong with either Formik, React Final Form, or React Hook Form.
This article is simply a roundup of form solutions available for React. If you need a more technical comparison, be sure to read the following: React Hook Form vs. Formik: A technical and performance comparison.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowThe useReducer React Hook is a good alternative to tools like Redux, Recoil, or MobX.
Node.js v22.5.0 introduced a native SQLite module, which is is similar to what other JavaScript runtimes like Deno and Bun already have.
Understanding and supporting pinch, text, and browser zoom significantly enhances the user experience. Let’s explore a few ways to do so.
Playwright is a popular framework for automating and testing web applications across multiple browsers in JavaScript, Python, Java, and C#. […]
6 Replies to "React form validation solutions: An ultimate roundup"
You can add Hookstate to the list: https://hookstate.js.org/docs/extensions-validation
Nice list you’ve got here! I’m using https://www.npmjs.com/package/react-use-form-state right now which I like very much!
You’re missing one of the best ones: formal, it supports react web and Native
Yeah, I just tried out `use-form-state` on a project and was pretty happy with it as a lighter-weight solution. Obviously not as full-featured as the other libs, but if you’re looking for something that’s small, removes the need to define a bunch of `onChange` handlers, and has good TS support, definitely try it out.
Small, efficient, with hooks
https://github.com/JoviDeCroock/Hooked-Form
The recurring problem I see with any those libraries is that:
– you need to learn how to use it
– your Js bundle gets heavier
– the code complexity grows
– it gets more difficult to maintain over the time, especially when you have longer forms
I think we can implement form validation/submission just using HTML5 features – no library needed:
https://medium.com/p/eb7977d81675
What do you guys think?