React Form Validation with the useForm Hook

React Form Validation with the useForm Hook

In general JavaScript form validation has never been a trivial task. From the days of VanillaJS you’d always have a million different options but none was the “standard” way of doing it, and so you as the developer were left to decide what worked best for you.

Lo and behold, almost a decade later, the situation hasn’t improved that much. Forms however, still need validation and frameworks are leaving the validation task to the user.

Form validation in React is no exception, if we do a quick search we can easily find over 10 different alternatives. This is of course, part of the experience pack you buy when you decide to work with any JS framework after all, the community will always give you options (whether that’s a good thing or not is, the jury is still out).

In this article though, I want to cover one of those particular options. I want to show you how to perform form validation in React using the useForm hook.

So let’s get down to it!

The React Hook Form library

The library we’re going to be using for this task is the React Hook Form library which provides a very intuitive and simple hook we can use to configure our form-validation rules.

One of the key reasons why I picked this library is because of the great dev experience it provides. Libraries need to be powerful and filled with features, yes, but if you as their user will have to jump through hoops just to get it up and running, then they’re not worth it. This is not the case for the React Hook Form though, as you’ll see in a minute, it’s very easy and straightforward to use.

What else is good about this library? Good question, let’s see:

  • It was built with performance in mind. This means that on the bundle size this library is tiny (8,6Kb after minimizing and gzipping it). At the same time, it also takes rendering performance very seriously by isolating the re-renders from the child components.
  • The team behind it is currently very active and under constant development. The library itself has over 26k stars and 1.3k forks on Github. This is not an outdated or forgotten library.
  • As the name itself shows, it follows React’s latest philosophy around hooks. You don’t have to worry about introducing foreign, or outdated, concepts into your logic. This library “just works”.

And if that wasn’t enough, the amazing team behind has created one of the best documentation sites available for a library. To the point that they even provide you with a form builder mini-app that you can use to generate the validation. All you have to do is go here and select your form fields, add the required validation configuration and the code to your right will auto-update.

The form builder in action

That said, let’s get started with our build, I’m going to assume you know how to create a React application, so I’ll skip that part. Just install the library into your project with

npm install react-hook-form

And that’s it, you’re ready to get started.

Adding basic validation to your form

While you can definitely go to the form builder and get all the code you need, you might already have your form built. If that’s the case, you’ll want to understand the basics behind the API provided by the library instead of blindly generating code that you don’t really know about.

There are two basic functions you’ll need to know, the rest is a bonus:

  • The useForm hook. This is your starting point, if you don’t call the hook, nothing happens. You can perfectly use this hook without any parameters, and the default settings will kick in and it’ll “just work”. However, if you want to customize its behavior you can check out all the configuration options available (they range from changing WHEN the validation happens to HOW the validation happens, so do check it out).
  • The register function. This is going to be your bread and butter. Your meat and potatoes of the entire validation logic. The hook won’t be able to validate a field unless it gets registered and with it, the validation options (is it required or optional? what type of validation does it need? and so on).

With those two functions, you can quickly get started. So let’s take a quick look at an example, I’m going to be adding validations to the following form:

The code for it, as it stands right now, is the following:

import React from 'react';
import { useForm } from 'react-hook-form';
import Form  from 'react-bootstrap/Form'
import Container from 'react-bootstrap/Container'
import { Button } from 'react-bootstrap';

import 'bootstrap/dist/css/bootstrap.min.css';

const {Group, Label, Control} = {...Form}
export default function App() {
  const { register, handleSubmit, formState: { errors } } = useForm();
  const onSubmit = data => console.log(data);
  return (
    <form onSubmit={handleSubmit(onSubmit)} className="simpleForm">
          First name
        <Control type="text" placeholder="First name" {...register("First name")} />

        <Label>Last Name</Label>
        <Control type="text" placeholder="Last name" {...register("Last name")} />
        <Control type="text" placeholder="Email" {...register("Email")} />
        <Label>Mobile number</Label>
        <Control  type="tel" placeholder="Mobile number" {...register("Mobile number")} />
        <Control as="textarea"  {...register("Your comments")} />

        <Button type="submit" >

As you can see I’m using React Bootstrap to style the whole thing. You can easily add it with this line:

npm install react-bootstrap bootstrap@5.1.3

And then just copy the code from above into your file, I have it inside my App.js file since this is a simple example, you can have it wherever you need to put your form on.

The example right now is not doing anything other than rendering the form like in the screenshot above, registering all fields with the form hook and finally calling the onSubmit handler when the button is clicked. Clearly, we’re not validating anything or showing any type of error. So let’s get to that, shall we?

Configuring the validation for each field

The neat thing about the React Hook Form library is that you don’t have to code any validation, not unless you need something super customized. The register function (told you that was an important one) accepts all kinds of options to specify exactly how to validate each field.

In our case, we’ll be adding the following validations:

  • First and last name: Make sure they’re single-word strings. Meaning, we won’t allow any whitespaces or non-alphabetic characters.
  • Email. We’ll try to provide some form of format validation for this one.
  • Mobile number. We’ll only take numbers for this field.
  • Comments. We’ll allow any type of characters, but with a max of 280 characters. To make it interesting, you know?

For that, as I already mentioned, we’ll add a second attribute to our register function calls. It’ll be a config object specifying a required attribute as true and the specific validation options.

Here are the function calls on their own for you to study:

//first name
<Control type="text" placeholder="First name" {...register("First name", {required: true, pattern:/^[a-zA-Z]+$/})} />

//last name
<Control type="text" placeholder="Last name" {...register("Last name", {required: true, pattern: /^[a-zA-Z]+$/, maxLength: 100})} />
<Control type="text" placeholder="Email" {...register("Email", {required: true, pattern: /^\S+@\S+$/i})} />

//mobile phone
<Control  type="tel" placeholder="Mobile number" {...register("Mobile number", {required: true, pattern:/^[0-9+-]+$/, minLength: 6, maxLength: 12})} />
<Control as="textarea"  {...register("Your comments", {required: true, maxLength: 280})} />

As you can appreciate, most of these rules need a regexp pattern to be checked. Sadly, if you don’t yet know how to read Regular Expressions you’ll have to once you start dealing with somewhat complex validation rules. That said, suffice it to say that in this scenario each expression is checking exactly what we said before. Also note that in some cases I’m also mixing the regular expression validation with length-based validation. While you can do that with the regExp as well, you’ll see in a minute that this way we can show more detailed error messages.

As of right now, if you were to add those register lines to the original file, you’ll automatically get the first input field with an error getting the focus event (which is already quite handy) and on the browser console, you’d see something like this:

Notice how we’re getting the list of fields that have an error. For each one, we get a message attribute, which is empty right now (more on that in a second), and we also get the type of the validation. This is especially useful if you, as I did, have multiple types of validations on the same field. This way you know exactly what went wrong.

While this is already quite powerful, we’re not done yet. The first step to any proper validation is to customize the error message, so let’s get to it.

Open Source Session Replay

OpenReplay is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data.


Start enjoying your debugging experience - start using OpenReplay for free.

Addin per-field custom error messages

Customizing the type of message we deliver to our users is a must if we actually care about the UX of our applications. This is why this library allows us to directly specify the messages we want to show on each type of validation.

As you’re about to see, you’ll have to write a bit more code, but the results are worth it. Again, I’m just going to show you the relevant bits of the code:

//the first name
<Control type="text" placeholder="First name" {...register("First name", {
          required: {
            value: true,
            message: "You must specify your first name before moving forward"
        pattern: {
          value: /^[a-zA-Z]+$/,
          message: "That's not a valid name where I come from..."
        })} />

//the last name
<Control type="text" placeholder="Last name" {...register("Last name", {
          required: {
            message: "Please, add your last name"
          pattern: {
            value: /^[a-zA-Z]+$/,
            message: "I don't think you understood exactly what to do here, did you?"
          maxLength: {
            value: 100,
            message: "That's way too long to be a real last name, try again"
        })} />

//the email address
<Control type="text" placeholder="Email" {...register("Email", {
          required: {
            value: true,
            message: "You need to specify a valid email address"
          pattern: {
            value: /^\S+@\S+$/i,
            message: "I think I said _valid_, didn't I?"
        })} />

//the mobile phone number
<Control  type="tel" placeholder="Mobile number" {...register("Mobile number", {
          required: {
            value: true,
            message: "Please add your mobile phone number, I won't call you, promise!"
          pattern: {
            value: /^[0-9+-]+$/,
            message: "This is not a valid mobile phone to me, try again!"
          minLength: {
            value: 6,
            message: "This number is too short, not gotta fly, try again"
          maxLength: {
            value: 12,
            message: "...And now it's too damn long, make sure the number is right, would you?"
          } })} />

//the comments field
<Control as="textarea"  {...register("Your comments", {
          required: {
            value: true,
            message: "I really wanna know what you think about this form, so leave a comment please!"
          maxLength: {
            value: 280,
            message: "But don't overdo it, 280 characters should be more than enough!"
        })} />

Notice how I’m still using the register function, but now instead of just specifying the type of validation and a single value, we turn that into an object and each type of validation receives:

  • The value field, with the relevant content (i.e for the required key is a boolean, for the max and min is going to be a number, and so on)
  • The message attribute, with the actual message to return when one of the fields fails to validate.

What’s even cooler, is that we get the formState object returned from the hook, and we can use that to display the validation errors which will be inside formState.errors.

Looking at the errors list, we can see that it contains the following information:

All we have to do now, is turn that JSON into a nice error message.

Displaying validation errors

To display the validation errors captured in the Hook Form’s formState.errors array, we can simply ask if the current state of the form is valid, and if it’s not, ask if it’s already been submitted. Otherwise we run the risk of trying to display errors when there are none.

So let’s quickly see how I implemented a simple alert box using React Bootstrap and the React Form Hook:

{/* alerting of errors */}
{(!formState.isValid && formState.isSubmitted) ?
<Alert variant="danger"   >
   {Object.values(formState.errors).map( (e,idx) => {
     return (<p key={idx}>{e.message}</p>)
<Alert variant="success"   >Please fill in the form</Alert>

I’m simply checking the isValid and isSubmitted properties of the form state. If they meet the criteria, I’ll show the alert of type "danger", otherwise I’ll show a nice green success box.

The end result looks like this:

Notice how once the submit is done, and the user is updating the fields, the error messages update on the onBlur event, keeping the UX quite dynamic.

And that’s it, by now, you should have everything you need to add validation to your forms with very few lines of code.

Check out their documentation to keep learning about the rest of the different customizations you can apply to the validation logic.