React Hook Form: The complete guide

 Forms are an essential part of how users interact with websites and web applications. Validating the user’s data passed through the form is a crucial responsibility for a developer.

React Hook Form is a library that helps you validate forms in React. It is a minimal library without any other dependencies, while being performant and straightforward to use, requiring developers to write fewer lines of code than other form libraries.

In this guide, you will learn how to use the React Hook Form library to build excellent forms in React without using any complicated render props or higher-order components. Feel free to jump ahead to any section in this tutorial:

What is React Hook Form?

React Hook Form takes a slightly different approach than other form libraries in the React ecosystem by adopting the use of uncontrolled inputs using ref instead of depending on the state to control the inputs. This approach makes the forms more performant and reduces the number of re-renders.

The package size is tiny (just 8.6 kB minified and gzipped) and it has zero dependencies. The API is very intuitive, which provides a seamless experience to developers. React Hook Form follows HTML standards for validating the forms using a constraint-based validation API.

Another great feature offered by React Hook Form is its painless integration with UI libraries because most libraries support the ref attribute.

To install React Hook Form, run the following command:

npm install react-hook-form

How to use React Hooks in a form

In this section, you will learn about the fundamentals of the useForm Hook by creating a very basic registration form.

First, import the useForm Hook from the react-hook-form package:

import { useForm } from "react-hook-form";

Then, inside your component, use the Hook as follows:

const { register, handleSubmit } = useForm();

The useForm Hook returns an object containing a few properties. For now, you only require register and handleSubmit.

The register method helps you register an input field into React Hook Form so that it is available for the validation, and its value can be tracked for changes.

To register the input, we’ll pass the register method into the input field as such:

<input type="text" name="firstName" {...register('firstName')} />

This spread operator syntax is a new implementation to the library that enables strict type checking in forms with TypeScript. You can learn more about strict type checking in React Hook Form here.

Versions older than v7 had the register method attached to the ref attribute as such:

<input type="text" name="firstName" ref={register} />

Note that the input component must have a name prop, and its value should be unique.

The handleSubmit method, as the name suggests, manages form submission. It needs to be passed as the value to the onSubmit prop of the form component.

The handleSubmit method can handle two functions as arguments. The first function passed as an argument will be invoked along with the registered field values when the form validation is successful. The second function is called with errors when the validation fails.

const onFormSubmit  = data => console.log(data);

const onErrors = errors => console.error(errors);

<form onSubmit={handleSubmit(onFormSubmit, onErrors)}>
 {/* ... */}
</form>

Now that you have a fair idea about the basic usage of the useForm Hook, let’s take a look at a more realistic example:

import React from "react";
import { useForm } from "react-hook-form";

const RegisterForm = () => {
  const { register, handleSubmit } = useForm();
  const handleRegistration = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(handleRegistration)}>
      <div>
        <label>Name</label>
        <input name="name" {...register('name')} />
      </div>
      <div>
        <label>Email</label>
        <input type="email" name="email" {...register('email')} />
      </div>
      <div>
        <label>Password</label>
        <input type="password" name="password" {...register('password')} />
      </div>
      <button>Submit</button>
    </form>
  );
};
export default RegisterForm;

As you can see, no other components were imported to track the input values. The useForm Hook makes the component code cleaner and easier to maintain, and because the form is uncontrolled, you do not have to pass props like onChange and value to each input.

You can use any other UI library of your choice for creating the form. But first make sure to check the docs, and find the prop used for accessing reference attribute of the native input component.

In the next section, you will learn how to handle form validation in the form we just built.

How to validate forms with React Hook Form

To apply validations to a field, you can pass validation parameters to the register method. Validation parameters are similar to the existing HTML form validation standard.

These validation parameters include the following properties:

  • required indicates if the field is required or not. If this property is set to true, then the field cannot be empty
  • minlength and maxlength set the minimum and maximum length for a string input value
  • min and max set the minimum and maximum values for a numerical value
  • type indicates the type of the input field; it can be email, number, text, or any other standard HTML input types
  • pattern defines a pattern for the input value using a regular expression

If you want to mark a field as required, you code should turn out like this:

<input name="name" type="text" {...register('name', { required: true } )} />

Now try submitting the form with this field empty. This will result in the following error object:

{
name: {
  type: "required",
  message: "",
  ref: <input name="name" type="text" />
  }
}

Here, the type property refers to the type of validation that failed, and the ref property contains the native DOM input element.

You can also include a custom error message for the field by passing a string instead of a boolean to the validation property:

// ...
<form onSubmit={handleSubmit(handleRegistration, handleError)}>
  <div>
      <label>Name</label>
      <input name="name" {...register('name', { required: "Name is required" } )} />
  </div>
</form>

Then, access the errors object by using the useForm Hook:

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

You can display errors to your users like so:

const RegisterForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm();
  const handleRegistration = (data) => console.log(data);

  return (
    <form onSubmit={handleSubmit(handleRegistration)}>
      <div>
        <label>Name</label>
        <input type="text" name="name" {...register('name')} />
        {errors?.name && errors.name.message}
      </div>
      {/* more input fields... */}
      <button>Submit</button>
    </form>
  );
};

Below you can find the complete example:

import React from "react";
import { useForm } from "react-hook-form";

const RegisterForm = () => {
  const { register, handleSubmit, formState: { errors } } = useForm();
  const handleRegistration = (data) => console.log(data);
  const handleError = (errors) => {};

  const registerOptions = {
    name: { required: "Name is required" },
    email: { required: "Email is required" },
    password: {
      required: "Password is required",
      minLength: {
        value: 8,
        message: "Password must have at least 8 characters"
      }
    }
  };

  return (
    <form onSubmit={handleSubmit(handleRegistration, handleError)}>
      <div>
        <label>Name</label>
        <input name="name" type="text" {...register('name', registerOptions.name) }/>
        <small className="text-danger">
          {errors?.name && errors.name.message}
        </small>
      </div>
      <div>
        <label>Email</label>
        <input
          type="email"
          name="email"
          {...register('email', registerOptions.email)}
        />
        <small className="text-danger">
          {errors?.email && errors.email.message}
        </small>
      </div>
      <div>
        <label>Password</label>
        <input
          type="password"
          name="password"
          {...register('password', registerOptions.password)}
        />
        <small className="text-danger">
          {errors?.password && errors.password.message}
        </small>
      </div>
      <button>Submit</button>
    </form>
  );
};
export default RegisterForm;

If you want to validate the field when there is an onChange or onBlur event, you can pass a mode property to the useForm Hook:

const { register, handleSubmit, errors } = useForm({
  mode: "onBlur"
});

Find more details on the useForm Hook in the API reference.

Usage with third-party components

In some cases, the external UI component you want to use in your form may not support ref, and can only be controlled by the state.

React Hook Form has provisions for such cases, and can easily integrate with any third-party-controlled components using a Controller component.

React Hook Form provides the wrapper Controller component that allows you to register a controlled external component, similar to how the register method works. In this case, instead of the register method, you will use the control object from the useForm Hook:

const { register, handleSubmit, control } = useForm();

Consider that you have to create a role field in your form that will accept values from a select input. You can create the select input using the react-select library.

The control object should be passed to the control prop of the Controller component, along with the name of the field. You can specify the validation rules using the rules prop.

The controlled component should be passed to the Controller component using the as prop. The Select component also requires an options prop to render the drop-down options:

<Controller
  name="role"
  control={control}
  defaultValue=""
  rules={registerOptions.role}
  render={({ field }) => (
    <Select options={selectOptions} {...field} label="Text field" />
  )}
/>

The render prop above provides onChangeonBlurnameref, and value to the child component. By spreading field into the Select component, React Hook Form registers the input field.

You can check out the complete example for the role field below:

import { useForm, Controller } from "react-hook-form";
import Select from "react-select";
// ...
const { register, handleSubmit, errors, control } = useForm({
  // use mode to specify the event that triggers each input field 
  mode: "onBlur"
});

const selectOptions = [
  { value: "student", label: "Student" },
  { value: "developer", label: "Developer" },
  { value: "manager", label: "Manager" }
];

const registerOptions = {
  // ...
  role: { required: "Role is required" }
};

// ...
<form>
  <div>
    <label>Your Role</label>
    <Controller
      name="role"
      control={control}
      defaultValue=""
      rules={registerOptions.role}
      render={({ field }) => (
        <Select options={selectOptions} {...field} label="Text field" />
      )}
    />
    <small className="text-danger">
      {errors?.role && errors.role.message}
    </small>
  </div>
</form>

You can also go through the API reference for the Controller component here for a detailed explanation.

Conclusion

React Hook Form is an excellent addition to the React open source ecosystem. It has made creating and maintaining forms much easier for developers. The best part about this library is that it focuses more on developer experience, and is very flexible to work with. React Hook Form also integrates well with state management libraries and works excellent in React Native.

That was it from this guide. You can check out the full code and demo for your reference here. Until next time, stay safe and keep building more forms. Cheers ✌

Comments

Popular posts from this blog

Visual Studio Code settings For Developers

Some useful links