Wern Ancheta Full-stack developer, fitness enthusiast, skill toy hobbyist.

The ultimate roundup of React form validation solutions

14 min read 4121

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’re going to take a look at some of the most popular solutions for form management and validation in React.

Formik

A roundup on React form solutions wouldn’t be complete without Formik. With over 22k 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 it’s 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 which 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 its validation code get 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

React Final Form is 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:

We made a custom demo for .
No really. Click here to check it out.

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

  • React Final Form doesn’t depend on any libraries
  • It allows you to manage which form elements gets notified of form state changes. This makes it very performant
  • Supports React Hooks
  • Supports schema-based validation through the Yup library
  • Relies on custom components that act as a wrapper for HTML form elements
  • Relies on React Context API for providing form state data to each form field
  • Final Form weighs 5.1Kb and React Final Form weighs 3.2Kb. This makes it very lightweight

Unform

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 it’s 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 uses version 2. Better yet, just stick to the official docs. This is because there are some tutorials that 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

  • Supports React Hooks — this makes the code easier to read and maintain
  • Supports React Native by using the @unform/mobile package
  • Uses HTML elements instead of custom components. Integration is through the use of the useField API
  • Provides easy access to form data, errors, and methods via a form ref
  • Supports complex form structures through the use of dot notation for the input name
  • Requires creation of custom form input components before it can be used
  • Unform core weighs 3.7Kb and Unform web weighs 606 bytes

React Form

React 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

  • Supports React Hooks through the use of useForm
  • Uses memoization for fast re-renders
  • React Form weighs 4.5Kb when minified and gzipped

react-json-schema-form

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:

  1. Specify your schema
  2. Add your custom formats and validation rules
  3. Use the component

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

  • Automatically generate forms and apply validation rules based on JSON schema
  • Supports Bootstrap and Material UI HTML semantics
  • Requires a bit of a learning curve due to its extensive API
  • React-json-schema-form weighs 1.3Kb when minified and gzipped

React Hook Form

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 has the least amount of code you have to write 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. First, you need to pass the name attribute which serves as the key to uniquely identify your input. Then pass a ref that uses the register function. 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, errors } = useForm();
  const onSubmit = data => console.log(data);
  // {"email":"abc@gmail.com"}

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="email" ref={register({ 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

  • Uses uncontrolled form validation for optimal performance
  • Aligns with the existing HTML standard for form validation through the use of validation rules such as required, min, max, pattern, and others
  • Supports React Native. Simply substitute HTML form elements with the React Native equivalent (eg. TextInput)
  • React Hook Form doesn’t depend on any libraries
  • Provides you with the ability to isolate components re-render for improved performance
  • Has a form builder utility if you want to quickly create trivial forms
  • Supports schema-based validation through the Yup library
  • Has great documentation and example usage
  • React Hook Form weighs 9Kb when minified and gzipped

Redux Form

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

  • Redux Form manages the form state through the Redux library
  • Heavier than most libraries mentioned here. It weighs 24.4Kb (minified + gzipped)
  • Redux Form can only be used with React and Redux
  • Redux Form is only useful if there’s a need for tight coupling between existing Redux state and form data

Formsy

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. Though the biggest downside with this library is that it doesn’t support React Hooks.

Installation

yarn add formsy-react

Basic usage

Formsy has a similarity with Unform as it requires you to create your own custom component that’s 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 has a valid input. In the example below, 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: "abc@gmail.com"}
  }

  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

  • Formsy is one of the older React form validation libraries out there
  • Includes its own validation rules like isEmail andisUrl
  • Requires creation of custom form input component before it can be used
  • Has no official support for React Hooks which makes the code look more complex
  • Formsy weighs 9.9Kb when minified and gzipped

Simple React Validator

Simple React Validator is a Laravel-inspired React form validation library. Out of all the libraries we’ve gone through 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

  • Lots of built-in validation rules that are based from Laravel
  • Uses the state for form state management
  • Has localization support for error messages
  • Has no official React Hooks support
  • Simple React Validator weighs 4Kb when minified and gzipped

Form validation libraries to avoid

If you’re starting a new project or is 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:

Other form libraries

Here’s a list of other form libraries to explore. These are mostly doing the same thing as the libraries we’ve gone through so I’ll just leave you to explore them for yourself:

Conclusion

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 was 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.

Full visibility into production React apps

Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.

The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.

Modernize how you debug your React apps — .

Wern Ancheta Full-stack developer, fitness enthusiast, skill toy hobbyist.

5 Replies to “The ultimate roundup of React form validation solutions”

  1. You’re missing one of the best ones: formal, it supports react web and Native

  2. 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.

Leave a Reply