Forms are one of the most important parts through which a user can interact with websites and applications. Validating user data sent through a form is an important responsibility of the developer.
In this article we tell you what react-hook-form is, how to create a form and use it as well as how to validate forms with react Hook Form.
What is react-hook-form
React-hook-form is a very flexible, productive, and easy-to-use library that has no other library dependencies. It also requires developers to write fewer lines of code than other form libraries.
React-hook-form takes a different approach from other form libraries in the react ecosystem. It applies the use of unsupervised form elements using ref instead of state dependency to control form fields. This approach makes forms more productive and reduces re-rendering.
How to use react-hook-form
We need to import from the package react-hook-form hook useForm which has many properties that help control the form. First, let's look at the most basic properties:
const { register, handleSubmit } = useForm();
Note that input must have a name attribute and its value must be unique.
The handleSubmit method, as the name implies, controls the submission of the form. It must be passed as a value to the onSubmit attribute of the form tag.
The handleSubmit method can handle two functions as arguments. The first function, passed as an argument, will be called along with the registered field values when the form is validated successfully. The second function is called with errors if validation fails.
const onFormSubmit = data => console.log(data);
const onErrors = errors => console.error(errors);
<form onSubmit={handleSubmit(onFormSubmit, onErrors)}>
{/* ... */}
</form>
Now that we have a basic understanding of the useForm hook, let's 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="userName" {...register('userName')} />
</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;
You can see from the code that no other components were imported to track input values. The useForm hook makes the component code cleaner and easier to maintain, and since the form is unsupervised, we don't need to pass attributes such as onChange and value for each field.
You can use any other UI library of your choice to create the form. But first, be sure to check the documentation for the prop used to access the ref attribute of the native input.
In the next part we will explain how to handle the validation of the form we just wrote.
How to validate forms with react-hook-form
In order to apply validation to a field we can pass validation parameters to the register method. The validation parameters are similar to the existing HTML form validation standard.
These validation parameters include the following properties:
- required specifies whether the field is required or not. If this property is set to true, the field cannot be empty
- minlength and maxlength set the minimum and maximum length for the string value of the field
- min and max sets the minimum and maximum values for numbers
- type specifies the type of the field; this can be email, number, text, or any other standard HTML input type
- pattern specifies the pattern for the input value using a regular expression
If we want to mark a field as required, our code should look like this:
<input
name="name"
type="text"
{...register('name', { required: true } )}
/>
Now let's try to send the form with an empty field. 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 its own input element.
We can also include a custom error message for the field by passing a string instead of a boolean value 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 we access the error object with the useForm hook:
const { register, handleSubmit, formState: { errors } } = useForm();
We can display errors for our users as follows:
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 is a 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="userName" {...register('userName', 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 we want to validate a field when the onChange or onBlur event occurs, we can pass the mode property to the useForm hook:
const { register, handleSubmit, errors } = useForm({
mode: "onBlur"
});
More information about the useForm hook can be found in the API help.
Using with third-party library components
In some cases the external UI component we want to use in our form may not support ref and may only be managed by state.
React-hook-form provides for such cases and can easily integrate with any third-party managed components using the Controller component.
React-hook-form provides a Controller wrapper component that allows you to register a managed external component similar to how the register method works. In this case, we will use the control object from the useForm hook instead of the register method:
const { register, handleSubmit, control } = useForm();
Suppose we need to create a "role" field in your form that accepts values from Select. Select can be created using the react-select library.
The control object must be passed to the prop control of the Controller component along with the name of the field. We can specify validation rules with the prop rules.
The controlled component (Select) must be passed to the Controller component with a prop as. The Select component also requires prop options to display drop-down options:
<Controller
name="role"
control={control}
defaultValue=""
rules={registerOptions.role}
render={({ field }) => (
<Select
options={selectOptions}
{...field} label="Text field"
/>
)}
/>
The above render prop passes onChange, onBlur, name, ref, and value to the child component. Through the field propagation statement in the Select component, the React-hook-form registers the input field.
We can see the full 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 для указания события, которое вызывает каждое поле ввода
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"
rules={registerOptions.role}
control={control}
defaultValue=""
render={({ field}) => (
<Select options={selectOptions} {...field} label="Txt" />
)}
/>
<small className="text-danger">
{errors?.role && errors.role.message}
</small>
</div>
</form>