Building Better Forms in React with react-hook-form

Forms are an essential part of most web applications, but building them in React can quickly get complex and tedious. As the number of form fields grows, so does the code needed to manage the form state, validate user input, and handle submission. This is where the react-hook-form library comes in.

react-hook-form simplifies the process of creating forms in React, reducing the amount of code you need to write while providing a better user experience. In this guide, we‘ll take an in-depth look at what react-hook-form is, why you should consider using it, and how to build forms with it, from the basics all the way to advanced use cases.

What is react-hook-form?

react-hook-form is a lightweight library that leverages React Hooks to help you build and validate forms. It was created by Beier Luo, a software engineer at Bytedance, to address some of the common pain points of building forms in React.

Some key features of react-hook-form include:

  • Easier form state management with less code
  • Built-in form validation with a simple API
  • Improved performance with minimal re-renders
  • Integration with UI libraries and other validation libraries

Since its initial release in 2019, react-hook-form has quickly gained popularity in the React community. As of writing, it has over 1.7 million weekly downloads on npm and 23k stars on GitHub.

Basic Usage

Let‘s start by looking at how to set up a basic form with react-hook-form. First, install the library in your React project:

npm install react-hook-form

Next, import the useForm hook in your form component:

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

The useForm hook is the core of react-hook-form. It provides a way to register form inputs and handle form submission. Here‘s a simple login form example:

function LoginForm() {
  const { register, handleSubmit } = useForm();

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

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <input name="email" ref={register} />
      <input name="password" type="password" ref={register} />
      <button type="submit">Submit</button>
    </form>
  );
}

In this example:

  1. We destructure the register and handleSubmit functions from the useForm hook.
  2. We define an onSubmit callback that will be called with the form data when the form is submitted.
  3. We pass handleSubmit(onSubmit) to the form‘s onSubmit prop. This tells react-hook-form to handle the form submission and pass the form data to our onSubmit callback.
  4. We register each form input by passing the register function to the ref attribute. This allows react-hook-form to track the value of each input.

With just this simple setup, react-hook-form is managing our form state and handling submission. We can access the form data in the onSubmit callback without having to manually track input changes or implement a submit handler.

Form Validation

Of course, most real-world forms need validation. react-hook-form makes adding validation easy. You can define validation rules for each input by passing them to the register function.

<input 
  name="email"
  ref={register({
    required: ‘Email is required‘,
    pattern: {
      value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i,
      message: ‘Invalid email address‘
    }
  })}
/>

Here we‘ve added two validation rules to the email input:

  1. required: The field must have a value. If not, the ‘Email is required‘ error message will be displayed.
  2. pattern: The field value must match the provided regex pattern (a simple email format check in this case). If not, the ‘Invalid email address‘ error message will be displayed.

react-hook-form provides several built-in validation rules, including:

  • required: The field must have a value
  • min/max: The field value must be greater than/less than the provided value
  • minLength/maxLength: The field value must have a length greater than/less than the provided value
  • pattern: The field value must match the provided regex pattern
  • validate: A custom validation function

To display validation errors to the user, we can use the errors object returned by the useForm hook. It will contain an entry for each field that failed validation, with the corresponding error message.

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

// ...

{errors.email && <span>{errors.email.message}</span>}

Refactoring Validation

As the number of form fields and validation rules grows, the JSX can start to get cluttered. We can refactor our validation logic to keep things maintainable.

One approach is to define our validation schema outside the JSX using a validation library like Yup. Yup allows us to define our validation schema in a declarative way and then pass it to react-hook-form.

import * as yup from ‘yup‘;

const schema = yup.object().shape({
  email: yup
    .string()
    .email(‘Invalid email address‘)
    .required(‘Email is required‘),
  password: yup
    .string()
    .min(8, ‘Password must be at least 8 characters‘)
    .required(‘Password is required‘),
});

function LoginForm() {
  const { register, handleSubmit, errors } = useForm({
    validationSchema: schema,
  });

  // ...
}

Now our JSX is much cleaner, and we can reuse the validation schema elsewhere if needed.

Custom Validation

Sometimes the built-in validation rules aren‘t enough. In these cases, we can write our own custom validation functions and pass them to the validate option of the register function.

const validateAge = value => {
  if (value < 18) {
    return ‘You must be at least 18 years old‘;
  }
  return true;
};

// ...

<input
  name="age"
  ref={register({
    validate: validateAge
  })}
/>

The custom validation function will receive the field value as an argument. It should return true if the value is valid, or an error message string if the value is invalid.

Performance

One of the standout features of react-hook-form is its performance. It‘s designed to minimize the number of re-renders that occur when form state changes.

In most form libraries, changing any form value will trigger a re-render of the entire form. This can lead to performance issues in larger forms. react-hook-form, on the other hand, only re-renders the components that have actually changed.

This performance boost is achieved through a combination of uncontrolled components (where the form state is managed by the DOM instead of React state), and subscription-based form state management.

Comparison to Other Form Libraries

There are many form libraries available for React, each with their own strengths and weaknesses. Two of the most popular alternatives to react-hook-form are Formik and redux-form.

Formik is a feature-rich form library that‘s been around longer than react-hook-form. It uses a higher-order component (HOC) and render props pattern, which can lead to more verbose code compared to react-hook-form‘s hook-based approach. Formik also tends to trigger more re-renders due to its reliance on controlled components.

redux-form is a form library built on top of Redux, a popular state management library for React. It provides a robust set of features, but it also inherits the complexity of Redux. Setting up redux-form involves a lot of boilerplate code, and it‘s tightly coupled to the Redux ecosystem.

In comparison, react-hook-form offers a simpler API, better performance, and less boilerplate. It‘s a good choice for projects that don‘t need all the features of Formik or the tight Redux integration of redux-form.

Advanced Use Cases

So far, we‘ve covered the basics of using react-hook-form. But the library also supports more advanced use cases, such as:

  • Array fields (dynamic lists of inputs)
  • Dependent fields (fields that appear/disappear based on other field values)
  • Conditional validation (validation rules that apply only in certain conditions)
  • Asynchronous validation (validation that involves an API call, like checking if a username is available)
  • Multi-step forms (forms divided into multiple pages/steps)
  • Accessibility improvements

While covering these advanced topics is beyond the scope of this article, the react-hook-form documentation provides examples and guides for each of these scenarios.

Conclusion

Building forms in React doesn‘t have to be a chore. By leveraging the power of react-hook-form, you can create complex, validated forms with less code, better performance, and a great user experience.

In this guide, we‘ve covered everything from the basics of react-hook-form to advanced use cases and comparisons to other popular form libraries. Hopefully, you now have a solid understanding of what react-hook-form is and how it can benefit your React projects.

So the next time you need to build a form in React, give react-hook-form a try. Its simple yet powerful API might just change the way you think about forms in React.

Happy coding!

Similar Posts