React form validation with React Hook Form and Yup

Validating user input on forms prior to submission, in my opinion, is one of the most important and fundamental things about a website these days.

Thank god we have several options to validate them, in the React ecosystem there are lots of libraries. However many of these libraries either end up having a huge boilerplate, which is sometimes scary, even when implementing in a form with few fields. Or they decrease application performance.

Keeping these points in mind, I always end up looking for a solution that is simple, with little boilerplate and that has a great performance.

Apart from that, another thing I'm looking for is a form validation library that lets you use a library to validate schemas, such as Joi, Yup, etc. This way I can reuse the schema code in the frontend and backend.

It's exactly for all these reasons that I love working with React Hook Form.

Let's code

First we will add the following dependencies to our React application:

npm install react-hook-form @hookform/resolvers yup

Now let's pretend this is your form:

import React from "react";

const App = () => {
  return (
    <form>
      <h2>Lets sign you in.</h2>
      <br />

      <input placeholder="email" type="email" required />
      <br />

      <input
        placeholder="password"
        type="password"
        required
      />
      <br />

      <button type="submit">Sign in</button>
    </form>
  );
};

export default App;

Now let's import React Hook Form into our project:

import React from "react";
import { useForm } from "react-hook-form";
// Hidden for simplicity

Then let's get the following things from the useForm() hook:

const App = () => {
  const { register, handleSubmit, formState: { errors }, reset } = useForm();
  return (
    // Hidden for simplicity
};

Quick overview

  • The register() method allows registering an element and applying the appropriate validation rules.
  • The handleSubmit() function will receive the form data if validation is successful.
  • The reset() function will clear all form fields or reset to initial values.
  • In this case, we are using formState to return form errors in an easier way.

Now we have to import Yup into our project and then let's create our schema.

// Hidden for simplicity
import * as yup from "yup";

const schema = yup.object().shape({
  email: yup.string().email().required(),
  password: yup.string().min(8).max(32).required(),
});

Now we have to import @hookform/resolvers so we can use our Yup schema to validate input values. Like this:

import { yupResolver } from "@hookform/resolvers/yup";

// Hidden for simplicity

const App = () => {
  const { register, handleSubmit, formState: { errors }, reset } = useForm({
    resolver: yupResolver(schema),
  });
  return (
    // Hidden for simplicity
};

Now we have to create our function to submit the data (which in this example will be a simple log). Just like we're going to add the reset() method inside our function so that form inputs are cleared as soon as they're submitted.

Lastly let's add the handleSubmit() method to our form. Similar to this:

const App = () => {
  const { register, handleSubmit, formState: { errors }, reset } = useForm({
    resolver: yupResolver(schema),
  });
  const onSubmitHandler = (data) => {
    console.log({ data });
    reset();
  };
  return (
    <form onSubmit={handleSubmit(onSubmitHandler)}>
      // Hidden for simplicity
    </form>
};

The next step is to register our inputs, assigning their names according to the properties of our schema:

const App = () => {
  // Hidden for simplicity
  return (
    <form onSubmit={handleSubmit(onSubmitHandler)}>
      <h2>Lets sign you in.</h2>
      <br />

      <input {...register("email")} placeholder="email" type="email" required />
      <br />

      <input
        {...register("password")}
        placeholder="password"
        type="password"
        required
      />
      <br />

      <button type="submit">Sign in</button>
    </form>
  );
};

Last but not least, let's add the error messages for each of the inputs:

const App = () => {
  // Hidden for simplicity
  return (
    <form onSubmit={handleSubmit(onSubmitHandler)}>
      <h2>Lets sign you in.</h2>
      <br />

      <input {...register("email")} placeholder="email" type="email" required />
      <p>{errors.email?.message}</p>
      <br />

      <input
        {...register("password")}
        placeholder="password"
        type="password"
        required
      />
      <p>{errors.password?.message}</p>
      <br />

      <button type="submit">Sign in</button>
    </form>
  );
};

Now with everything finished, the code should look like this:

import React from "react";
import { useForm } from "react-hook-form";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";

const schema = yup.object().shape({
  email: yup.string().email().required(),
  password: yup.string().min(8).max(32).required(),
});

const App = () => {
  const { register, handleSubmit, formState: { errors }, reset } = useForm({
    resolver: yupResolver(schema),
  });
  const onSubmitHandler = (data) => {
    console.log({ data });
    reset();
  };
  return (
    <form onSubmit={handleSubmit(onSubmitHandler)}>
      <h2>Lets sign you in.</h2>
      <br />

      <input {...register("email")} placeholder="email" type="email" required />
      <p>{errors.email?.message}</p>
      <br />

      <input
        {...register("password")}
        placeholder="password"
        type="password"
        required
      />
      <p>{errors.password?.message}</p>
      <br />

      <button type="submit">Sign in</button>
    </form>
  );
};

export default App;

In order for you to have an idea of the final result, you should have something similar to what you see in the gif:

What about you

What library do you use to validate your forms in React?

14