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. React-hook-form is a minimal library without any other dependencies. It is 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 prop or higher-order components.
React Hook Form: Introduction
React Hook Form takes a slightly different approach than other form libraries in the React ecosystem. React Hook Form adopts 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 very tiny and minimal: just 9.1KB minified + gzipped, and it has zero dependencies. The API is very intuitive that provides a seamless experience to developers when working with forms. React Hook Form follows HTML standards for validating the forms using constraint-based validation API.
Another great feature offered by React Hook Form is its painless integration with UI libraries, since most libraries support ref
.
To install React Hook Form, run the following command:
npm install react-hook-form
Simple register 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 few properties. For now, you only require register
and handleSubmit
.
The register
method helps you register an input field into the React Hook Form so that it is available for the validation and its value can be tracked for changes. To register the input, pass the register
method to ref>/code> prop of the input.
<input type="text" ref={register} name="firstName" />
An important point to note here is 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 a prop 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, take a look at a more real-world example:
import React from "react"; import { useForm } from "react-hook-form"; import { Form, FormGroup, Label, Input, Button } from "reactstrap"; const RegisterForm = () => { const { register, handleSubmit } = useForm(); const handleRegistration = (data) => console.log(data); return ( <Form onSubmit={handleSubmit(handleRegistration)}> <FormGroup> <Label>Name</Label> <Input name="name" innerRef={register} /> </FormGroup> <FormGroup> <Label>Email</Label> <Input type="email" name="email" innerRef={register} /> </FormGroup> <FormGroup> <Label>Password</Label> <Input type="password" name="password" innerRef={register} /> </FormGroup> <Button color="primary">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 since the form is uncontrolled, you do not have to pass props like onChange
and value
to each input.
In the example above, the reactstrap
library is used for building the form UI. You may notice here that the register
method was passed to the innerRef
prop instead of ref
; this is because the reactstrap
form components give access to the native DOM input using the innerRef
prop.
You can use any other UI library of your choice for creating the form. Make sure to check the prop for accessing the reference to the native input component.
In the next section, you will learn how to handle form validation in the above form.
Handling Validation and Errors
To apply validations to a field, you can pass validation options to the register method. The validation options are similar to the existing HTML form validation standard.
The validation options 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
andmaxlength
sets the minimum and maximum length for a string input value.min
andmax
– Sets the minimum and maximum values for 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 can write something as follows.
<Input name="name" innerRef={register({ required: true })} />
On submit, this will result in the following error object.
{ name: { type: "required" message: "" ref: <INPUT name="name" type="text" class="form-control"></INPUT> } }
Here, the type
property refers to the type of the failed validation, and the ref
property contains the native DOM input element.
You can also pass an error message for the field by passing a string instead of a boolean to the validation property.
// ... <Form onSubmit={handleSubmit(handleRegistration, handleError)}> <FormGroup> <Label>Name</Label> <Input name="name" innerRef={register({ required: "Name is required" })} /> </FormGroup> </Form>
You can also access the error object from the useForm
Hook.
const { register, handleSubmit, errors } = useForm();
Below you can find the complete example.
import React from "react"; import { useForm } from "react-hook-form"; import { Form, FormGroup, Label, Input, Button } from "reactstrap"; const RegisterForm = () => { const { register, handleSubmit, 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)}> <FormGroup> <Label>Name</Label> <Input name="name" innerRef={register(registerOptions.name)} /> <small className="text-danger"> {errors.name && errors.name.message} </small> </FormGroup> <FormGroup> <Label>Email</Label> <Input type="email" name="email" innerRef={register(registerOptions.email)} /> <small className="text-danger"> {errors.email && errors.email.message} </small> </FormGroup> <FormGroup> <Label>Password</Label> <Input type="password" name="password" innerRef={register(registerOptions.password)} /> <small className="text-danger"> {errors.password && errors.password.message} </small> </FormGroup> <Button color="primary">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" });
You can 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 the form may not support ref
and can only be controlled by the state.
React Hook Form has provisions for such use cases, and can easily integrate with any third party controlled component.
React Hook Form provides a wrapper component called Controller
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, errors, 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 as
prop will inject onChange
, onBlur
, and value
prop to the component. The Select
component also requires an options
prop to render the drop-down options. So, you can add the options
prop directly to the Controller
component and it will be passed to the Select
component, along with any other additional props.
<Controller name="role" control={control} as={Select} options={selectOptions} defaultValue="" rules={registerOptions.role} />
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({ mode: "onBlur" }); const selectOptions = [ { value: "student", label: "Student" }, { value: "developer", label: "Developer" }, { value: "manager", label: "Manager" } ]; const registerOptions = { // ... role: { required: "Role is required" } }; // ... <FormGroup> <Label>Your Role</Label> <Controller name="role" control={control} as={Select} options={selectOptions} defaultValue="" rules={registerOptions.role} /> <small className="text-danger"> {errors.role && errors.role.message} </small> </FormGroup>
You can 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 ✌
References
Full visibility into production React apps
Debugging React applications can be difficult, especially when users experience issues that are difficult to reproduce. If you’re interested in monitoring and tracking Redux state, automatically surfacing JavaScript errors, and tracking slow network requests and component load time, try LogRocket.

LogRocket is like a DVR for web apps, recording literally everything that happens on your React app. Instead of guessing why problems happen, you can aggregate and report on what state your application was in when an issue occurred. LogRocket also monitors your app's performance, reporting with metrics like client CPU load, client memory usage, and more.
The LogRocket Redux middleware package adds an extra layer of visibility into your user sessions. LogRocket logs all actions and state from your Redux stores.
Modernize how you debug your React apps — start monitoring for free.