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.
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.
- 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:
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:
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.
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.
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:
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:
We successfully implemented several key functionalities using React Hook Form and understood how it works:
- Field Registration: Learnt how to register fields within the form with the help of the
register
. - Form Submission: By using the
handleSubmit
function, we enabled form submission. - Validation: We implemented validation to prevent users from submitting invalid fields.
- Displaying Errors: By extracting
errors
fromformState
, 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,