Unlocking the Potential of React Hook Form: Unveiling, Benefits, and Setting Up Forms [Part 1]

Dive into the world of React Hook Form and transform your form-handling experience.

Vatsal Dave
Simform Engineering
9 min readMay 20, 2024

--

Forms are the backbone of countless web applications, serving as a gateway for user interaction and data submission. However, as developers, we often have to deal with complex form-handling logic such as validation, data transformation, conditional rendering, and performance issues.

In this comprehensive guide, we’ll explore the power and versatility of React Hook Form. Let’s dive in and learn how to handle forms in React applications using React Hook Form.

What is React Hook Form?

React Hook Form is a super lightweight (only 8.6kb minified and gzipped) form managing library with zero dependencies. It integrates seamlessly into your React application without using higher-order components or render props.

Moreover, the API is intuitive and provides a great developer experience with Typescript support. React Hook Form also provides a form builder and Dev Tool to watch and debug form state.

Advantages of React Hook Form

React Hook Form stands out for its performance, reducing re-renders and requiring less setup code than other form libraries.

Less Code: Managing and validating forms becomes a breeze. You only need to use the useForm hook to handle form setup, validation, and submission. This eliminates much of the complex configurations.

Improved Performance: React Hook Form follows an uncontrolled form methodology, capturing refs through the register function. Unlike controlled components, which re-render on every change, this approach minimizes re-renders.

Isolates re-render: By using ref instead of state, React Hook Form effectively reduces re-renders caused by user input in form fields. It also isolates components to prevent unnecessary re-rendering of other child components.

Faster Mounting: Components mount faster compared to Formik and Redux Form, with approximately 13% and 25% speed advantages, respectively. This speed is due to using uncontrolled inputs, which reduce overhead. Unlike controlled components, React Hook Form manages the input state internally. Its small size also ensures faster loading and execution.

Now that we’ve explored the significant advantages of React Hook Form. Let’s learn how to implement it in your application.

Form Setup and Installation

We’ll set up our React project using Vite, a front-end tool for building web applications faster. For more info on Vite, visit their website.

  1. Open your terminal and navigate to the directory where you want to install the project. Then paste this command. This will create our project react-hook-form-tutorial with the required dependencies (React, TypeScript):
npm create vite@latest react-hook-form-tutorial --template-react-ts

Now, go into the project’s directory, install the dependencies, and run the development server with the commands given below:

cd react-hook-form-tutorial
npm install
npm run dev

2. Clean up the default code from App.tsx, and create a form component UserDetailsForm.tsx in src/components/ which will contain a basic HTML form with a username and email input field with a submit button.

const UserDetailsForm = () => {
return (
<div>
<form>
<div className="form-control">
<label htmlFor="username">Username</label>
<input type="text" id="username" name="username" />
</div>
<div className="form-control">
<label htmlFor="email">Email</label>
<input type="email" id="email" name="email" />
</div>
<div>
<button>
Submit
</button>
</div>
</form>
</div>
);
};

export default UserDetailsForm;

3. Import this component in App.tsx:

import UserDetailsForm from "./components/UserDetailsForm";

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

export default App;

4. Install the react-hook-form package:

npm install react-hook-form

Note: The version of React Hook Form in this tutorial is 7.

Now that we have installed it, let’s use it to manage our UserDetailsForm.

Using the useForm Hook

First, define the form in the UserDetailsForm by importing the useForm hook:

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

const UserDetailsForm = () => {
const form = useForm(); //defining form
return (
...
...
)
};

export default UserDetailsForm;

The useForm hook is a powerful hook to manage the form with minimal re-renders. It returns an object with methods and properties to control and validate the form, including:

  • register: A method to register the field and apply validation.
  • formState: An object which contains information about the entire form state.
  • watch: A method to watch specified inputs, useful for determining what to render by condition.
  • handleSubmit: A method that will receive form data on successful validation and form errors and invalidation.
  • reset: A method to reset the entire/partial form state.

and many more.

You can read more about useForm from the official documentation.

Registering Fields with the useForm Hook

Let’s begin with the register function to register our form fields.

Destructure the register function from the form object and register the username field by passing the name attribute of the field as shown below:

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

const UserDetailsForm = () => {
const form = useForm(); //defining form
const { register } = form; // destructuring form object
const { name, ref, onBlur, onChange } = register("username"); // registering field
return (
...
...
)
};

export default UserDetailsForm;

By invoking register and passing the field’s name attribute to it, we have registered our field. The register function returns properties like name, ref, onBlur, and onChange, which are automatically applied to the input:

const UserDetailsForm = () => {
const form = useForm(); //defining form
const { register } = form; // destructuring form object
const { name, ref, onBlur, onChange } = register("username"); // registering field
return (
<div>
<form>
<div className="form-control">
<label htmlFor="username">Username</label>
<input
type="text"
id="username"
name={name}
ref={ref}
onChange={onChange}
onBlur={onBlur}
/>
</div>
...
...
</form>
</div>
);
};

export default UserDetailsForm;

Notice that we have replaced the name attribute with the property provided by the register and passed the rest of the properties. Now, React Hook Form will start listening to the changes made to the username field.

That's it! We've learned how to register our fields in the React Hook Form, but what if we have too many input fields? We’d have to destructure all the properties from the register function and pass it to every field. That would be too much unwanted code.

But there’s a simpler way, which is to directly spread the register function in the input field, so we will have less code. Let’s implement that pattern for the email field by replacing the name attribute:

const UserDetailsForm = () => {
const form = useForm(); //defining form
const { register } = form; // destructuring form object
const { name, ref, onBlur, onChange } = register("username"); // registering field
return (
<div>
<form>
<div className="form-control">
<label htmlFor="username">Username</label>
<input
type="text"
id="username"
name={name}
ref={ref}
onChange={onChange}
onBlur={onBlur}
/>
</div>
<div className="form-control">
<label htmlFor="email">Email</label>
<input
type="email"
id="email"
// spreading register directly here
{...register("email")} />
</div>
<div>
<button>
Submit
</button>
</div>
</form>
</div>
);
};

export default UserDetailsForm;

You can even implement the same for the username field now. Here, we will keep it as it is for example purposes.

After this, the form output will be:

Form output
Form output

The useForm hook also accepts an optional argument in the form of an object, enabling users to configure the validation strategy before submitting the form, few properties are defaultValues, mode, and resolver.

You can check all the properties here in the API documentation.

Submitting form

To submit the form, use the handleSubmit method provided by the form. The handleSubmit method receives form data on successful form validation, provided validation has been applied. It accepts two callbacks, one for submission and the other for error handling.

const { register, handleSubmit } = form;

We’ll create a submit handler to log the form data to the console. It will receive the parameter formdata from handleSubmit. However, in a real-life scenario, you will send form data to the server or perform other operations.

const onSubmitHandler = (formdata: {name:string, email:string}) => {
console.log("Form data:", formdata);
};

Next, assign handleSubmit to the onSubmit event of the form element, passing onSubmitHandler as an argument, as demonstrated below:

const UserDetailsForm = () => {
const form = useForm(); //defining form
const { register, handleSubmit } = form; // destructuring form object
const { name, ref, onBlur, onChange } = register("username"); // registering field

const onSubmitHandler = (formdata) => {
console.log("Form data:", formdata);
};

return (
<div>
<form onSubmit={handleSubmit(onSubmitHandler)}>
...
...
</form>
</div>
);
};

export default UserDetailsForm;

Now, when submitting, you will see the formdata in the console, as shown in the output below:

Logging form data in the console
Logging form data in the console

We can also submit empty form data. But that’s not what we want. We need to validate fields before the user hits submit. Hence, let’s add validation for form fields in the next section.

Validating fields

The register also accepts an optional argument as an object for field validation. It consists of properties such as required, validate, maxLength, minLength, and more.

You can read more about register here.

Using the optional argument, let’s add validation to the username field:

const { name, ref, onBlur, onChange } = register("username", {
required: { value: true, message: "Username is required", },
});

Let’s review our code; the validation object consists of a property required responsible for validating the username field. Within that property, we define value where true indicates a required field. Additionally, the message defines the error message to return if the condition is unmet. Similarly, add validation for the email field.

<input
type="email"
id="email"
// destructuring register function on the field itself to reduce code
{...register("email", {
required: { value: true, message: "Email is required" },
})}
/>

Now, when you hit submit, nothing logs. This indicates our validation is functioning correctly, and it prevents us from submitting invalid data.

But how do we display the errors we have defined? We can retrieve errors from the formState object within the form object. Let’s see how to display errors to the user.

Displaying validation errors

As the name suggests, the formState object provides information regarding the form’s current state, which helps us track user interaction. Let’s retrieve it from the form object and extract the errors object from the formState object that contains any validation errors.

More about formState.

const { register, handleSubmit, formState } = form; // destructuring formState
const { errors } = formState; // extracting errors object

Use the errors object to display the error of each form field below it by adding a p tag below the input.

...
...
<input
type="text" id="username" name={name} ref={ref} onChange={onChange}
onBlur={onBlur}
/>
<p className="text-red-500">{errors.username?.message}</p>
...
...
<input
type="email"
id="email"
// destructuring register function on the field itself to reduce code
{...register("email", {
required: { value: true, message: "Email is required" },
})}
/>
<p className="text-red-500">{errors.email?.message}</p>
...
...

The errors object has field names as properties, each with its corresponding message. The conditional ?. operator shows the error only when the field is invalidated.

Now, when we hit submit, the fields will get validated, and errors will be shown below fields as shown below:

Displaying errors when submitted with invalid data.
Displaying errors when submitted with invalid data.

Now that we have added validation and error display. Let’s integrate the Dev Tool provided by React Hook Form.

Integrating Dev Tool

The Dev Tool provides insights into the form’s behavior, making debugging and optimization easier. To integrate Dev Tool, install it as a dev dependency:

 npm install -D @hookform/devtools

Now, add the DevTool component to your UserDetailsForm:

import { DevTool } from "@hookform/devtools";

const UserDetailsForm = () => {
const form = useForm(); //defining form
const { register, handleSubmit, formState, control } = form; // destructuring form object
...
...
return (
<div>
<form>
...
...
</form>
<DevTool control={control} />
</div>
);
};

export default UserDetailsForm;

Here, we have imported the DevTool component and added it below the form element. We have also passed the control prop to it by extracting it from the form object. You may see the Dev Tool on your site containing the form state as shown below:

DevTools integration
Dev Tool integration

We successfully implemented several key functionalities using React Hook Form and understood how it works:

  1. Field Registration: Learnt how to register fields within the form with the help of the register.
  2. Form Submission: By using the handleSubmit function, we enabled form submission.
  3. Validation: We implemented validation to prevent users from submitting invalid fields.
  4. Displaying Errors: By extracting errors from formState, we displayed validation errors below their respective fields.

You can check out the full source code on the Github Repository.

You can see the live demo here.

Conclusion

We’ve successfully integrated React Hook Form into our project, learning how to register fields, submit forms, implement validation, and display errors.

However, we’ve only scratched the surface. There are more advanced topics like complex field validation, default values, custom validation, conditional fields, and setting form values. We’ll cover these in the upcoming parts of the series, so stay tuned.

Meanwhile, follow the Simform Engineering Blog for more updates on the latest tools and technologies,

Follow us: Twitter | LinkedIn

--

--