React Hook Form - A Complete Guide

Depending on the use case, implementing forms can be pretty complex. That's why we wrote this guide to help you get started.

GraphCMS Favicon
Hygraph Team
React Hook Form

What is React Hook Form

Most applications have forms to collect data. Depending on the use case, implementing forms can be pretty complex, especially when building enterprise-level applications that provide an intuitive and flawless user experience. As a developer, you have likely stumbled across building forms in most of your projects. Of course, you can manage simple forms on your own, but after a point, it becomes sensible to use a library that abstracts a lot of work in handling complex forms.

React Hook Form is one such library that helps to manage complex forms. It has excellent performance, is super lightweight, has zero dependencies, can be easily integrated with different React UI frameworks like Material, Antd, and provides an intuitive API and excellent developer experience.

How To Use React Hook Form

To get started, install the library with npm i --save react-hook-form

Using it inside your component is super simple, import the useForm hook from the library

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

The useForm hook gives us access to many properties, but for now, we will only use register and handleSubmit.

const { register, handleSubmit } = useForm();

Register an input element with a variable name like this, this will make the value of input available for form validation and form submission.

<input
className="border-2 outline-none p-2 rounded-md"
placeholder="Name"
{...register("name")}
/>

Then you can handle your submit your form this way and create an onSubmitmethod which will have access to the forms data.

<form onSubmit={handleSubmit(onSubmit)}>

Here’s a complete example

import { useState } from "react";
import { useForm } from "react-hook-form";
export default function ReactHookFormMini() {
const { register, handleSubmit } = useForm();
const [data, setData] = useState("");
const onSubmit = (data) => {
setData(data)
}
return (
<div className="w-full flex justify-center items-center bg-gray-900 p-8 border-r border-dashed">
<div className="w-1/2 shadow-lg rounded-md bg-white p-8 flex flex-col" style={{height:'375px'}}>
<h2 className="text-center font-medium text-2xl mb-4">
React Hook Form Basic
</h2>
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-1 flex-col justify-evenly"
>
<input
className="border-2 outline-none p-2 rounded-md"
placeholder="Name"
{...register("name")}
/>
<button
className=" flex justify-center p-2 rounded-md w-1/2 self-center bg-gray-900 text-white hover:bg-gray-800"
type='submit'
>
<span>
Submit
</span>
</button>
</form>
<div className='h-4'>
<p> Data: {JSON.stringify(data)}</p>
</div>
</div>
</div>
);
}

This is how it will look, we have used tailwind for the CSS, feel free to use your own CSS frameworks.

React Hook Basic Form

Adding Validations

Validations are part and parcel of almost every form, they’re an application’s first line of defense against unwanted data. Validations ensure that incorrect data doesn’t reach the backend servers and, eventually the databases. Ideally, most software data is validated at every layer, i.e., the Frontend, Backend, and Database.

It can become tedious and repetitive for large forms to manage validations for each field and their error states. React Hook Form provides excellent API options and also aligns with the existing HTML standard for form validation, here is the list of validation rules supported by the library:

  • required - The input value is mandatory or not
  • min - The minimum value that can be accepted
  • max - The maximum value that can be accepted
  • minLength - The minimum length of input that can be accepted
  • maxLength - The minimum length of input that can be accepted
  • pattern - A regex pattern to validate the input
  • validate - Any custom function to validate the input

A simple example to use these attributes

<input
className="border-2 outline-none p-2 rounded-md"
placeholder="Phone Number"
{...register("phoneNumber", {
minLength: 1,
maxLength: 10,
})}
/>

Also, you can pass error messages with the validation rule, this will make displaying errors very simple.

<input
className="border-2 outline-none p-2 rounded-md"
placeholder="Email"
{...register("email", {
pattern: {
value: /^.*@hygraph.com$/,
message: 'Email must end with hygraph.com'
}
})}
/>

You can get the error object from useForm and display the error message in the template.

const { formState: { errors } } = useForm();
<span className="text-sm text-red-700">
{errors?.email?.message}
</span>

React Hook Basic Form Validations

Integrating with Other Components

It is possible that you have existing components and UI Frameworks in place, you can integrate React Hook Form with those frameworks. The below example shows how to integrate React Hook Form with AntD’s components.

import React, { useState } from 'react';
import { Controller, useForm } from "react-hook-form";
import { Input, Checkbox, Slider } from "antd";
export default function RFHWithAntd() {
const { control, handleSubmit } = useForm();
const [data, setData] = useState({});
const onSubmit = (data) => {
setData(data)
}
return (
<div className="w-full flex justify-center items-center bg-gray-900 p-8 border-r border-dashed">
<div className="w-1/2 shadow-lg rounded-md bg-white p-8 flex flex-col" style={{ height: '375px' }}>
<h2 className="text-center font-medium text-2xl mb-4">
React Hook Form With AntD
</h2>
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-1 flex-col justify-evenly"
>
<Controller
control={control}
name="name"
render={({ field }) => (
<Input
{...field}
placeholder="Name"
/>
)}
/>
<div className="flex">
<label>Is Developer?</label>
<Controller
control={control}
name="isDeveloper"
render={({ field: { value, onChange } }) => (
<Checkbox
className="ml-4"
checked={value}
onChange={(e) => {
onChange(e.target.checked);
}}
/>
)}
/></div>
<section>
<label>Experience In Years</label>
<Controller
control={control}
name="experience"
render={({ field }) => <Slider {...field} />}
/>
</section>
<button
className=" flex justify-center p-2 rounded-md w-1/2
self-center bg-gray-900 text-white hover:bg-gray-800"
type='submit'
>
<span>
Submit
</span>
</button>
</form>
<div className='h-4'>
<p> Data: {JSON.stringify(data)}</p>
</div>
</div>
</div>
)
};

React Hook Form With AntD

React Hook Form Examples

We mentioned earlier that React Hook Form delivers excellent performance, one of the reasons for the same is how the library manages re-renders. Compared to any other library or an implementation of a Form in React from scratch, React Hook Form hardly performs any re-renders when values inside your form change, which is an excellent optimization to have out of the box. We can go through an example to establish this, we will build two forms with the same functionality. One with implementation from scratch and another with React Hook Form, then compare the re-renders for the same input.

Normal form

import React, { useState } from 'react';
let renderCount = 0
export default function NormalForm() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [showFormData, setShowFormData] = useState(false)
const onSubmit = (event) => {
event.preventDefault()
setShowFormData(true)
console.log('Run Validations Manually, maintain & show errors on UI, if all good make API call.')
}
renderCount += 1
return (
<div className="w-1/2 flex justify-center items-center bg-gray-900 p-8">
<div className="w-full shadow-lg rounded-md bg-white p-8 flex flex-col" style={{ height: '375px' }}>
<h2 className="text-center font-medium text-2xl mb-4">
Normal Form
</h2>
Render Count -- {renderCount}
<form
onSubmit={onSubmit}
className="flex flex-1 flex-col justify-evenly"
>
<input
className="border-2 outline-none p-2 rounded-md"
placeholder="Name"
value={name}
onChange={(e) => { setName(e.target.value) }}
/>
<input
className="border-2 outline-none p-2 rounded-md"
type="email"
placeholder="Email"
value={email}
onChange={(e) => { setEmail(e.target.value) }}
/>
<button
className=" flex justify-center p-2 rounded-md
w-1/2 self-center bg-gray-900 text-white hover:bg-gray-800"
type='submit'
>
<span>
Submit
</span>
</button>
</form>
{
<div className='h-4'>
<p> Data:
{
showFormData ?
<span> {name} {email} </span>
: null
}
</p>
</div>
}
</div>
</div>
);
}

React Hook Form

import { useState } from "react";
import { useForm } from "react-hook-form";
let renderCount = 0
export default function ReactHookForm() {
const { register, handleSubmit } = useForm();
const [data, setData] = useState("");
const onSubmit = (data) => {
setData(JSON.stringify(data))
}
renderCount += 1
return (
<div className="w-1/2 flex justify-center items-center bg-gray-900 p-8 border-r border-dashed">
<div className="w-full shadow-lg rounded-md bg-white p-8 flex flex-col" style={{ height: '375px' }}>
<h2 className="text-center font-medium text-2xl mb-4">
React Hook Form
</h2>
Render Count -- {renderCount}
<form
onSubmit={handleSubmit(onSubmit)}
className="flex flex-1 flex-col justify-evenly"
>
<input
className="border-2 outline-none p-2 rounded-md"
placeholder="Name"
{...register("name")}
/>
<input
className="border-2 outline-none p-2 rounded-md"
placeholder="Email"
{...register("email")}
/>
<button
className=" flex justify-center p-2 rounded-md
w-1/2 self-center bg-gray-900 text-white hover:bg-gray-800"
type='submit'
>
<span>
Submit
</span>
</button>
</form>
<div className='h-4'>
<p> Data: {data}</p>
</div>
</div>
</div>
);
}

At the start. without any insertions in the form.

Comparision between regular and react hook form - Start

After inserting the same values in the form and hitting submit.

Comparision between regular and react hook form - End

React Hook Form took 4 re-renders while the normal form took 58, the difference is substantial. Also, this is a very simple form with two fields, so you can imagine that the re-renders in a complex form will be even more.

Advanced Example

To strengthen our understanding, we can go one step further and explore the other available API options.

TheuseForm hook provides a formState property which gives us more details about the user’s interaction with our form.

  • isDirty - a boolean value that denotes if the user interacted with the form
  • isValid - a boolean value denotes if the current form state is valid.
  • errors - an object that contains errors if any validations fail.
  • isSubmitted - a boolean value that denotes if the user has tried to submit the form at least once
  • touchedFields - an object which holds the touched fields.
  • dirtyFields - an object which holds the dirty fields.

The difference between dirty and touched fields is that If the user clicks on text input, it’s marked as touched but not dirty, after that, if the user also enters any value inside the text input, then only it is marked as dirty.

The useForm hook also provides a watch function, which can be used to dynamically watch on form inputs and display other fields. Dynamic forms are an everyday use case, where form fields are shown dynamically based on what users enter in previous fields.

Here’s a complete example showing the use of all the above properties.

import { useState } from 'react';
import { useForm } from 'react-hook-form';
let renderCount = 0
export default function ReactHookFormAdvanced() {
const {
register,
handleSubmit,
formState: { touchedFields, isDirty, isValid, dirtyFields, isSubmitted, errors },
watch
} = useForm();
const [data, setData] = useState('');
const watchIsDeveloper = watch('isDeveloper');
renderCount += 1
return (
<div className='w-full flex justify-center items-center bg-gray-900 p-8'>
<div className='w-2/3 shadow-lg rounded-md bg-white p-8 flex flex-col justify-start' style={{ height: '700px' }}>
<h2 className='text-center font-medium text-2xl mb-4'>
React Hook Form Advanced
</h2>
Render Count -- {renderCount}
<form
onSubmit={handleSubmit(setData)}
className='flex flex-1 flex-col justify-evenly'
>
<input
className='border-2 outline-none p-2 rounded-md'
placeholder='Name'
{...register('name')}
/>
<input
className='border-2 outline-none p-2 rounded-md'
placeholder='Email'
{...register('email', { required: 'Email is required.' })}
/>
<input
className='border-2 outline-none p-2 rounded-md'
placeholder='Phone Number'
{...register('phoneNumber')}
/>
<div>
<span className='mr-4'>
Are you a developer?
</span>
<input type='checkbox' {...register('isDeveloper')} />
</div>
{
watchIsDeveloper ?
<div className='flex w-full '>
<input
className='flex-1 border-2 outline-none p-2 rounded-md mr-2'
placeholder='Experience (Years)'
{...register('exp_years')} />
<input
className='flex-1 border-2 outline-none p-2 rounded-md'
placeholder='Experience (Months)'
{...register('exp_months')} />
</div>
: null
}
<button
className=' flex justify-center p-2 rounded-md
w-1/2 self-center bg-gray-900 text-white hover:bg-gray-800'
type='submit'
>
<span>
Submit
</span>
</button>
</form>
<p className='w-4/5'> <strong> Data: </strong> {JSON.stringify(data)} </p>
<p> <strong> Is Valid: </strong> {JSON.stringify(isValid)}</p>
<p> <strong> Is Dirty : </strong> {JSON.stringify(isDirty)} </p>
<p> <strong> Is Submited: </strong> {JSON.stringify(isSubmitted)}</p>
<p> <strong> Errors: </strong> {JSON.stringify(errors?.email?.message)}</p>
<p> <strong> Dirty Fields : </strong> {JSON.stringify(dirtyFields)} </p>
<p> <strong> Touched Fields: </strong> {JSON.stringify(touchedFields)} </p>
<p> <strong> Watching Is Developer: </strong> {JSON.stringify(watchIsDeveloper)}</p>
</div>
</div>
);
}

Advanced React Hook Form - Clean

And filled:

Advanced React Hook Form - Filled