DEV Community

Cover image for Form validation with react-hook-form
M. Akbar Nugroho
M. Akbar Nugroho

Posted on

Form validation with react-hook-form

Form is one of core functionalities that must exist in any application. This functionality allows us interact with user data (input) and process it into a valuable data or resource (output).

Even this functionality is powerful, it has one biggest obstacle. Yes, it's how to validate the user input. And that's the reason why I make this article.

In this article I will share to you how we can handle form validation specifically in React application using a package called react-hook-form.

Let's start it out!

Requirements

  • React application (fresh or existing app)

NOTE:
I will use my previous project called example-app. It has no features. Just a fresh React project installed using CRA.

Steps

1. Add react-hook-form

I use Git inside example-app. So, before adding the package I will create a new branch called feat-signin then merge that feature into branch main whenever I finish that feature.



# Create and checkout to branch feat-signin
git checkout -b feat-signin


Enter fullscreen mode Exit fullscreen mode

Now, it's time to add the package.



yarn add react-hook-form


Enter fullscreen mode Exit fullscreen mode

2. Create the form

Maybe you have a clue about what form that I want to build. Yes, it's a sign-in form. I will validate the user email and password before they can actually sign-in.

I will not using any styling tools. Just HTML to make it simple and focus :).

First, I wanna add a page called Signin inside my project.



# Here's my current project
.
├── package.json
├── public
│   ├── favicon.ico
│   ├── index.html
│   ├── logo192.png
│   ├── logo512.png
│   ├── manifest.json
│   └── robots.txt
├── README.md
├── src
│   ├── App.css
│   ├── App.js
│   ├── App.test.js
│   ├── index.css
│   ├── index.js
│   ├── logo.svg
│   ├── pages
│   │   └── Signin.js
│   ├── reportWebVitals.js
│   └── setupTests.js
└── yarn.lock


Enter fullscreen mode Exit fullscreen mode

Create the sign-in form and import it inside App.js.



// pages/Signin.js
function Signin() {
  return (
    <div>
      <form>
        <div>
          <label htmlFor="email">Email</label>
          <input type="email" id="email" />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <input type="password" id="password" />
        </div>
        <div>
          <button>Signin</button>
        </div>
      </form>
    </div>
  );
}

export default Signin;


Enter fullscreen mode Exit fullscreen mode


// App.js
import Signin from './pages/Signin';

function App() {
  return <Signin />;
}

export default App;


Enter fullscreen mode Exit fullscreen mode

3. Integrate the form with react-hook-form

Let's integrate the previous form so we can collect all user input inside that form.



// pages/Signin.js
import { useForm } from 'react-hook-form';

function Signin() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm();

  const onSubmit = (form) => {
    console.log(form);
  };

  return (
    <div>
      <form onSubmit={handleSubmit(onSubmit)}>
        <div>
          <label htmlFor="email">Email</label>
          <input type="email" id="email" {...register('email')} />
        </div>
        <div>
          <label htmlFor="password">Password</label>
          <input type="password" id="password" {...register('password')} />
        </div>
        <div>
          <button>Signin</button>
        </div>
      </form>
    </div>
  );
}

export default Signin;


Enter fullscreen mode Exit fullscreen mode

Run the application, open the browser console and try to submit the form. You will see something like image below.
Submit form

4. Add input validation

If I submit the form with an empty password, I will not get any error message that indicate I forgot to input my password which is a bad UX.
Forgot to input password

To achieve that feature, we need to install two packages. @hookform/resolvers/yup and yup.



yarn add @hookform/resolvers yup


Enter fullscreen mode Exit fullscreen mode

NOTE:
yup is one of JavaScript schema Objects. It allows us to define a shape (structure) and validate a JavaScript object. You can also use other schema object like Joi, Zod, etc.

Now, let's add input validation into the form.



// pages/Signin.js

// ...
import { yupResolver } from '@hookform/resolvers/yup';
import * as yup from 'yup';

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

function Signin() {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({ resolver: yupResolver(schema) });

// ...
}
// ...


Enter fullscreen mode Exit fullscreen mode

Now, if we submit the form with an empty password, we will not see any message in the browser console because the form is actually throw error messages and not submit the form.
Form error

Look, even the cursor automatically focus to the input element that produce the error.

5. Displaying the error messages

Now, the form can validates the input value, but it's not good enough. We need to display what's wrong with the form. So, the user can input the correct value.



// pages/Signin.js

<div>
  <label htmlFor="email">Email</label>
  <input type="email" id="email" {...register("email")} />
  {errors.email?.message && <span>{errors.email.message}</span>}
</div>
<div>
  <label htmlFor="password">Password</label>
  <input type="password" id="password" {...register("password")} />
  {errors.password?.message && <span>{errors.password.message}</span>}
</div>


Enter fullscreen mode Exit fullscreen mode

Submit the form and we can see the error messages.
Form with error messages

Hmm, I think the error messages not user friendly enough, right? So, let's improve it.

6. Customize the error messages

This is the reason why I choose Yup for the schema validation. We can easily customize the error messages like this.



// pages/Signin.js

// ...
const schema = yup.object().shape({
  email: yup
    .string()
    .email('Please provide a valid email address')
    .required('Please provide your email address'),
  password: yup.string().required('Please provide your password'),
});
// ...


Enter fullscreen mode Exit fullscreen mode

Submit the form again and you will see the error messages has changed.
Improved error messages

Bonus

Validating the form on the client side is not enough. We also need to validate the form on the server side because attacker can bypass our validation on the client side.

The problem comes when we want to display the error messages from the server into the form. Fortunately, we can easily done this feature using react-hook-form.

We just need to use setError API to display the error messages that comes from the server into the form.



// pages/Signin.js

// Response from the server
// {
//   "message": "...",
//   "errors": {
//     email: ["The email must be a valid email address."]
//   }
// }

// ...
const {
  register,
  handleSubmit,
  formState: { errors },
  setError,
} = useForm({ resolver: yupResolver(schema) });

const onSubmit = async (form) => {
  // Do sign-in process. Just example :)
  await Http.post(/** ... */);

  if ((await Http.status()) === 422) {
    let res = await Http.response();

    for (let [field, messages] of Object.entries(res.errors)) {
      setError(field, { type: 'manual', message: message.join(' ') });
    }
  }

  // ...
};
// ...


Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
bluebill1049 profile image
Bill

Thank you for the awesome tutorial.

Collapse
 
thexdev profile image
M. Akbar Nugroho

You're welcome, Bill!