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?
- How to use React Hooks in a form
- How to validate forms with React Hook Form
- Usage with third-party components
- Conclusion
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 totrue
, then the field cannot be emptyminlength
andmaxlength
set the minimum and maximum length for a string input valuemin
andmax
set the minimum and maximum values for a numerical valuetype
indicates the type of the input field; it can be email, number, text, or any other standard HTML input typespattern
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 onChange
, onBlur
, name
, ref
, 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
Post a Comment