Web Forms With React - Usman Abdur Rehman
Web Forms With React - Usman Abdur Rehman
This work is subject to copyright. All rights are solely and exclusively
licensed by the Publisher, whether the whole or part of the material is
concerned, specifically the rights of translation, reprinting, reuse of
illustrations, recitation, broadcasting, reproduction on microfilms or in any
other physical way, and transmission or information storage and retrieval,
electronic adaptation, computer software, or by similar or dissimilar
methodology now known or hereafter developed.
The publisher, the authors and the editors are safe to assume that the advice
and information in this book are believed to be true and accurate at the date
of publication. Neither the publisher nor the authors or the editors give a
warranty, expressed or implied, with respect to the material contained
herein or for any errors or omissions that may have been made. The
publisher remains neutral with regard to jurisdictional claims in published
maps and institutional affiliations.
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2025
U. A. Rehman, Web Forms with React, Apress Pocket Guides
https://doi.org/10.1007/979-8-8688-1224-8_1
1. Forms in React
Usman Abdur Rehman1
(1) Islamabad, Pakistan
Forms are an integral part of any software including the Web and are the
way of data entry for all the user data you see on the Web. From social
media posts to ecommerce products to YouTube videos/blog posts, most
data on the Web is present because of the data that was entered through
forms. It is therefore vital that you know how to properly handle forms,
how to properly scale them, how to handle validations for them, etc.
This chapter talks about the usual approach taken by novice developers
for handling forms in React and the cons of it.
Handling State
Listing 1-1 shows you what a traditional form looks like with native state
handling (using useState).
Handling Validation
Listing 1-2 shows you how traditional validation is performed in forms.
Standard
As discussed in the previous two sections, there is no standard for
performing validations, handling state, or making a form in React. When
that is the case, there is a high chance every developer on your team would
use a different strategy to make forms. Since everything can be done in a
million different ways, every person on the team would use their strategy to
implement a certain feature, which, when the feature would be worked on
again in the future, to fix a bug or to add an enhancement, would not
present an ideal solution since not everyone is familiar with that strategy the
developer used to code the feature.
Learning
Even if you have a particular custom form-making strategy/standard in your
company, if you get stuck somewhere, the only way to get through that is to
consult your team members. There would be no resources on the Internet
regarding learning it or fixing bugs if you ever encounter one.
Robustness
Each and every part of your web application must be robust and not error-
prone. Using a nonstandard way of making forms won’t help achieve that
objective.
The Solution
The solution for every con that we face as described above is using a library
that is used by millions of developers around the world. Using a library
gives us the following advantages:
1. You would have a standard for building forms, and that standard would
be easily adaptable and learnable by developers who would be joining
your team. Following a standard would ensure there would be a certain
set of practices that would be used by developers in your team so the
chances of something going wrong would be minimal as well.
2. This would ensure that the application you are building is robust since a
library with millions of downloads, hundreds of issue resolutions, etc.
is ideally bug-free and has been through the rigorous cycle of
development that every software practice goes through.
Summary
In this chapter we discussed the current form-making strategies in React in
detail and why it is not recommended to make forms that way.
In the next chapter, we will take a look at a very popular and widely
used React form library, React Hook Form, and we will see how it
addresses all our concerns that we discussed in this chapter.
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2025
U. A. Rehman, Web Forms with React, Apress Pocket Guides
https://doi.org/10.1007/979-8-8688-1224-8_2
In the last chapter, we looked at the cons of using our custom approaches to
manage form actions like state management, validations, etc. We saw how
using traditional ways of building forms can make our forms less robust and
scalable and how not following a standard brings nonuniformity to your
application.
In this chapter, we will discuss React Hook Form, a library that is being
used by millions of developers to make performant and robust forms. We
will take a look at why I chose this library and what it brings to the table.
Performance
React state on every update rerenders the whole component, which means
every calculation and subcomponent would be recalculated and rerendered
if not properly memoized.
Normal React form libraries like Formik use React state for managing
field values, which means that for every keystroke in an input field, for
example, you are rerendering the whole form, which should not be the case
but that is how state works. This results in a decrease in performance as the
form size increases if the sub-form components are not properly memoized.
React Hook Form on the other hand uses refs to manage form values.
Every input component in your form in React Hook Form is an
uncontrolled component. If you type in an input field in React Hook Form,
it does not trigger any rerender and only keeps track of the form values
using refs, which makes it much faster than other form libraries.
Also because React Hook Form has uncontrolled form components,
there is less overhead, and components mount very quickly.
GitHub Stats
The GitHub stats of any library dictate a lot regarding whether that library
is popular in the developer community, whether its maintenance has
stopped or not, whether it’s being worked on frequently or not, etc. In this
section, we will take a look at some of these GitHub stats, which dictate
that React Hook Form is the best form library for React.
GitHub stars serve as a measure of a library’s popularity, quality, and
likeness. A developer is most likely to use a library with more stars. At the
time of writing, React Hook Form has 39.1k GitHub stars as opposed to the
33.4k GitHub stars of Formik.
React Hook Form is maintained at a regular interval. Whenever you
open its GitHub page, you will see that the latest commit was made almost
one to two days ago, which means it’s being currently worked on for either
bug/issue fixes or new features and improvements. Its counterparts, Formik
and Redux Form, have the last commits from last year, which speaks about
the level of maintenance they receive.
The number of GitHub issues for React Hook Form at the time of this
writing is 1. This means it’s a robust library and doesn’t get a lot of issues
reported or even if issues are reported, they are being dealt with by the team
as quickly as possible. However, for Formik and Redux Form at the time of
this writing, they have 690 and 475 open issues, respectively, and a large
portion of them are bugs.
For React Hook Form, the number of open pull requests (PRs) is very
low because the pull request merge ratio is very high. For Formik the
number of open pull requests is very high, which means it is not being
maintained by its developers. For Redux Form, the open pull requests are
from 2023 meaning not a lot of people are actively using this library. You
can check out the GitHub stats for React Hook Form and Formik in Figure
2-1.
Figure 2-1 GitHub stats for React Hook Form and Formik
npm Installs
React Hook Form slowly and gradually is becoming the most used React
form library out there. As a reference, in April 2023, both Formik and React
Hook Form had almost similar downloads (around 2 million). However, at
the time of this writing, React Hook Form has double the downloads
(around 5 million) than those of Formik (around 2.7 million) as depicted in
Figure 2-2.
Figure 2-2 npm installs for React Hook Form and Formik as of March 17, 2024
It should be good practice to use a library that is the most popular/used
because it helps with a variety of things. The most popular library would
have the most learning material and Stack Overflow questions on the
Internet meaning developers would be up and running in no time.
Type Safety
React Hook Form is built in TypeScript, so it provides end-to-end type
safety for everything. If we specify a type object for our form values, every
function or value given to us by React Hook Form for that form would
adhere to that type signature.
As you can see in Figure 2-3, a type FormData has been defined with
two properties firstName and lastName, both having a type of string.
Because of that when a string param is passed in the setValue function
alongside the lastName key, TypeScript does not give any error.
However, TypeScript does throw an error when the wrong type is used with
the firstName key in the setValue function (Boolean) as well as when
we try to access a property bill that doesn’t belong in the FormData
type we passed in the form.
Figure 2-3 TypeScript giving errors on wrong field names + value types
You don’t have to worry about what setValue and errors are. We
will take a detailed look at them in the next chapters.
Zero Dependencies
React Hook Form has 0 third-party dependencies. Normally libraries like
Formik have tons of third-party dependencies. The problem with having
third-party dependencies is that if one of them has a bug, this could
introduce a bug in the dependent library as well. This makes React Hook
Form more robust than its counterparts. Also, the installation time + module
size would be much less since no third-party libraries would be installed
along with it.
Validation
React Hook Form has a variety of ways to validate your form state. You can
validate your inputs using HTML5 validation; popular third-party
validation libraries like Yup, Joi, Zod, etc.; and custom validation solutions.
Also, along with that, you have diverse options of validation strategies like
applying validation on change, blur, touched, submit, or all as well as the
ability to choose revalidation strategies.
Formik on the other hand can validate using custom validation solutions
or Yup only. Even if you want to apply some easy validations like required,
min, max, etc., without any third-party library, you will have to write
JavaScript code for it, and you can’t use a simple solution like passing an
object with required, min, and max properties and so on.
Subscriptions
React Hook Form provides you with the ability to subscribe to individual
input state changes without the need to rerender the entire form on state
change. Normally if you want to monitor any state change, you need to
have a useEffect that runs on state change. That means that you must have a
state for every input, which changes on every keystroke (like in Formik and
others).
Even though React Hook Form has uncontrolled React form
components (using refs), it still gives us the ability to subscribe to
individual field state changes, which makes it very powerful.
This has been explained in a brilliant way in Figure 2-4. The four
checkboxes can be considered as four individual child components of the
form. Components 1 and 3 are subscribed to the input field, so whenever
any value of that field would change, these components would rerender and
get the updated value, which can be used by them to do anything like make
a query to the backend, show or hide something, etc. Components 2 and 4
however are not subscribed to the input field (these components are not
concerned with the input field’s data), so they would not rerender at all.
This is what makes React Hook Form a performant library.
Figure 2-4 A figure showing field subscriptions in individual components in action
Cross-Platform
React Hook Form is available for both mobile and web (React Native and
React). A library for building forms for both major platforms with the same
API is a pretty cool thing to have in your arsenal.
Dev Tools
React Hook Form has amazing dev tools as depicted in Figure 2-5, which
can be used to monitor state changes related to the form fields. It also can
scroll to that exact input if that input’s label is clicked in the dev tools. It
can help debug our form state without the need to add logging statements
everywhere in our code.
Figure 2-5 An example form built using React Hook Form and its dev tools
Form Builder
React Hook Form has an amazing form builder on its website. You can
specify which inputs and their corresponding simple validations you want
to add to your form, and it generates the code for it. If the form you are
making is generic without any extra complicated functionality, you can use
the form builder to make your form. It can also be used to make a
boilerplate for your form so that you can add the other complicated stuff in
that form builder–generated code.
Features Comparison
All the features we discussed are summarized in Table 2-1.
Table 2-1 Features Comparison Between React Hook Form and Formik
Features React Hook Form Formik
Performance Fast (refs) Medium (state)
Type safety Available for everything Available but not for functions like
setFieldValue, setFieldError, etc.
Validation Built-in, Zod, Joi, Yup, Superstruct Yup + custom
+ custom
npm downloads (at the Around 5 million Around 2.7 million
time of writing)
GitHub stats (at the 39.1k GitHub stars 33.4k GitHub stars
time of writing) Few or no open PRs Hundreds of open PRs
Good issue resolution Bad issue resolution
Maintenance + continuous new No maintenance and new feature
version releases development
Dependencies 0 8
Subscriptions Ability to subscribe to individual Ability to subscribe to individual
inputs without rerendering the inputs while rerendering the whole
whole form form
Dev tools ✅ ✖
Form builder ✅ ✖
Summary
In this chapter, we discussed the React Hook Form library and, via
comparison with libraries like Formik and Redux Form, saw what features
it brings to the table like performance, type safety, etc. and how it’s better
than the others.
In the next chapter, we will take a look at the basics of React Hook
Form and even build a basic form by the end of that chapter.
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2025
U. A. Rehman, Web Forms with React, Apress Pocket Guides
https://doi.org/10.1007/979-8-8688-1224-8_3
In the last chapter, I compared React Hook Form with other React form
libraries like Formik and Redux Form and pointed out the differences these
libraries had in terms of performance, implementation under the hood,
popularity, support, etc.
In this chapter, I will discuss the basics of React Hook Form. You will
look at some core API of this library to see how you can use it.
The Core
The core of the React Hook Form library is the useForm hook. This hook
returns everything you need to build your form, from refs for the input
fields, which would be used to do validations, to individual change and blur
handlers for the inputs, the submit handler for the form, validation errors,
etc.
In this section, I will inspect the useForm hook in detail so that you
can see what params it expects, what it returns, and how it works overall.
Basic Usage
Here is a basic example of how you would make a form using React Hook
Form.
type Inputs = {
name: string;
email: string;
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input placeholder="Name"
{...register("name")} />
<input placeholder="Email"
{...register("email")} />
<input type="submit" />
</form>
);
};
Listing 3-1 Basic form built using React Hook Form
In this example form as can be seen in Figure 3-1. there are two input fields
Name and Email. I have used the register and handleSubmit
functions returned from the useForm hook to handle this form. The
register and handleSubmit functions would be used for form state
management and form submission, respectively. These functions would be
explained in detail in their respective sections later in this chapter.
Using TypeScript
As mentioned in the previous chapter, React Hook Form is a TypeScript-
compatible library, which means that you should use types with it to garner
its full power. You can see in the start of Listing 3-1 that there is a type
Inputs with two properties name and email both having a type of
string. These two type properties indicate that there are two input fields in
this form that expect a type string as their value (text field).
Then that type has been passed in the useForm hook as a type generic.
This ensures that whichever property or function you would use from the
useForm hook, it would adhere to the type you provided + would provide
auto completions for object properties, which have to correspond to the type
property names in the type passed in the useForm hook. Furthermore, if
you would use any field key not present in the type passed in the useForm
hook, you would get a TypeScript error, which would be good for
developing robust forms.
You can also not pass any type to the useForm hook if you are using
JavaScript or if you don’t want to. Passing types in useForm is not
mandatory. However, I would recommend you use types with React Hook
Form to get appropriate linting/auto completions/errors in your IDE/text
editor.
register
const { onChange, onBlur, name, ref } =
register("firstName");
Listing 3-2 The register function
The register function as you can see in Listing 3-2, is one of the
core functions returned by the useForm hook. This function powers every
input field in the form. A string key has to be passed to this function as a
param, which should be unique to the form. The register function when
called returns the corresponding onChange, onBlur, name, and ref for
the respective field.
This ref is the key to how React Hook Form behaves. The whole point
of React Hook Form is that it is more performant than other libraries, and
using refs is the reason that is. As discussed in the previous chapter, React
Hook Form does data management of the form using refs and not state. This
ref returned by the register function is responsible for writing the data
to the fields if some initial values are provided to the useForm hook plus
getting the data in the onSubmit function and so on (more details on this
in a later section).
The onChange and onBlur functions and the name string are
responsible for keeping track of the data that is being entered and, in turn,
do various operations like applying validation for that data (on change or on
blur). You will look at validations in more detail in Chapter 5. The name
string has the same string value that we pass to the register function.
This register function also takes in an object as a second param,
which is optional where you can supply basic native validation rules like if
the field should be required, the field should have a certain length constraint
and so on (more on this in future chapters).
handleSubmit
<form onSubmit={handleSubmit(onSubmit)}></form>
Listing 3-3 The handleSubmit function
watch
watch("name");
watch(["name", "email"]);
Listing 3-4 The watch function
This function would watch for field value changes. This function
expects a string value or values (as an array) as a param (as can be seen in
Listing 3-4), which should coincide with any of the field names. If any of
the fields corresponding to the field names passed in the watch function
would change, then the entire form would rerender, and you would be able
to do anything you want to do based on those field changes (e.g., using a
useEffect to fetch new records based on a value change in a dropdown in
your form); otherwise, the component would not rerender at all because of
the ref model that React Hook Form is based on.
This is awesome since you can only decide to rerender the form
component for specific input changes and not all input changes (like in
Formik as discussed in Chapter 2), which boosts the performance of your
forms.
If you want to rerender only a child component based on a field change,
then a better way to go about this would be to use the useWatch hook. I
will discuss this in a future chapter.
formState
const { errors, isLoading, isValid, ...rest } =
formState;
Listing 3-5 The formState object
reset
The reset function can be used to reset the entire form state to whatever
was originally specified as initial values (via the defaultValues param)
in the useForm hook. If nothing was supplied in the defaultValues
param, then this function would empty all the fields.
The reset function also takes in an optional param. If you want that
the form’s state resets to something other than the defaultValues, you
can pass that object to the reset function as a param.
useForm Params
The useForm hook also expects some params, and they can be divided
into two categories: form value–related params and validation-related
params. We are going to look at validation in detail in Chapter 5. For now,
let’s just only discuss the form value params.
defaultValues
useForm({
defaultValues: {
firstName: '',
lastName: ''
}
})
useForm({
defaultValues: async () => fetch('/api-
endpoint');
})
Listing 3-6 The defaultValues param
values
useForm({
values: {
firstName: '',
lastName: ''
}
})
Listing 3-7 The values param
This param as can be seen in Listing 3-7, will react to changes, and
every time this param would change, the useForm hook would forward
this change to the entire form. This param is useful when you want to
change your form state based on some external change like a prop value
being passed from another component, some global state, or some query
data coming from a library that works with hooks (TanStack Query).
Summary
In this chapter, we looked at the basics of how React Hook Form works. We
explored the core of React Hook Form, the useForm hook. We looked at a
basic example form, built using that hook, and explored its API.
In this next chapter, we will dive into React Hook Form a bit more by
actually building some forms with React Hook Form.
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2025
U. A. Rehman, Web Forms with React, Apress Pocket Guides
https://doi.org/10.1007/979-8-8688-1224-8_4
In the last chapter, I discussed the core of React Hook Form, the useForm
hook. I discussed how you can use the useForm hook to make a basic
form and what each object and function returned by the useForm hook is
for.
In this chapter, I will make a couple of forms using React Hook Form. I
would first describe what I want to build, and then I will build those forms
step by step using React Hook Form so that you can understand it
thoroughly.
BMI Calculator
In this section, I will develop a BMI calculator. This form would have the
following salient features:
A form with two number fields, height and weight.
The height and weight fields would be required since both of those
values are required to calculate the BMI. The respective validation errors,
for example, height is required, and so on would be shown below each
field when validation would be performed.
Upon form submission, the BMI would be calculated based on the
formula Weight in kilograms/(Height in meters)2, which would then be
shown alongside the submit button.
First of all, I will write the HTML for the form. It would contain a React
component that would return a form tag with two input fields and a submit
button inside as seen in Listing 4-1.
input {
margin-bottom: 10px;
padding: 12px;
border-radius: 4px;
border: 1px solid black;
display: block;
}
p {
margin: 0;
}
.error {
margin-bottom: 5px;
color: red;
font-size: 14px;
}
.footer {
display: flex;
align-items: center;
gap: 12px;
}
.result {
font-size: 14px;
}
Listing 4-2 CSS styles for the BMI form
The form would look something like Figure 4-1.
Let’s start adding code related to React Hook Form to our form. First of all,
I will define the type for the useForm hook as given in Listing 4-3. Since
there are two fields in our form (height and weight) and both are of type
number, the type for the form would look something like the following.
interface FormData {
height: number;
weight: number;
}
Listing 4-3 FormData type for useForm
I will then call the useForm hook inside the BMI component and pass
the FormData type there as you can see in Listing 4-4.
<input
type="number"
{...register("height", { required: true,
valueAsNumber: true })}
placeholder="Height (in meters)"
step="any"
/>
<input
type="number"
{...register("weight", { required: true,
valueAsNumber: true })}
placeholder="Weight (in kg)"
step="any"
/>
Listing 4-5 Spreading the register function in input fields
As you can see, in addition to the first param in the register
function (field key), I have also passed an object as a second param with
two key–value pairs, required:true and valueAsNumber:true.
The required property if true would add a validation in the form
for that field where you won’t be able to submit a form if there is no text
entered in that particular field.
By default, any value inside an input text field would be a string value.
However, I need number values for the text I would enter inside the
height/weight fields. valueAsNumber:true would parse the value
entered to a number, because of which I would get number values for height
and weight inside the onSubmit function.
Now I would like to show the validation errors in the form. I would
show the individual validation errors below each field. I would get the
validation errors from the formState object, which is returned from the
useForm hook as seen in Listing 4-4.
The errors variable would be an object. The key would be the field
name, and the value would be an error object of the following type as can
be seen in Listing 4-6.
{
message: string;
type: string;
ref: React.RefObject;
}
Listing 4-6 Shape of the error object
where the type property would have the value required in case of
required validation. For this particular form, I will check if the errors
object has a field key corresponding to the field (there is an error). Then I
will check if the type property of that object has the value required. If
that is so, I will show an error for the corresponding field below it as can be
seen in Listing 4-7.
<input
type="number"
{...register("height", { required: true,
valueAsNumber: true })}
placeholder="Height (in meters)"
step="any"
/>
{errors.height?.type === "required" && (
<p className="error">Height is
required</p>
)}
<input
type="number"
{...register("weight", { required: true,
valueAsNumber: true })}
placeholder="Weight (in kg)"
step="any"
/>
{errors.weight?.type === "required" && (
<p className="error">Weight is
required</p>
)}
Listing 4-7 Mapping the errors
Now I would add the onSubmit function for the form. I will pass in a
handleSubmit function as a prop value to the onSubmit function of
the form component with a custom onSubmit function as a param where
I will write the code for form submission. This function would receive the
form data, and I would use the formula I mentioned in the form description
I provided to calculate the value.
After calculating the value, I will set a state, which I would display
alongside the BMI label in the form footer as can be seen in Listing 4-8.
return (
<form onSubmit={handleSubmit(onSubmit)}>
Listing 4-8 The form submission logic
If you now enter a valid height and weight like 3 meters and 50
kilograms and click the Calculate button, you will see the BMI 5.6 being
shown alongside the BMI: label like in Figure 4-2.
And that is it. Just like that I was able to make a basic form using React
Hook Form.
Signup Form
In this section, I will build a Signup form. This form would have the
following salient features:
A form with four fields, first name, last name, email, and password.
The first name, email, and password fields would be required. The email
field would have an email pattern validation so that it only allows valid
email to be entered in the text field. The password field would have a
minimum length validation of at least ten characters so that the password
is long. The respective validation errors, for example, the first name is
required, and so on would be shown below each field when validation
would be performed.
Upon form submission, the form data would be sent to a dummy
endpoint /signup.
During submission the submit button text would change from Signup to
Submitting …. During submission or when no input field has been
modified, the submit button would be disabled.
A third-party UI components library (Material UI) would be integrated.
The basic HTML structure of the form would look like the following as
can be seen in Listing 4-9.
<button type="submit">Signup</button>
</form>
);
}
Listing 4-9 HTML skeleton for the Signup form
.form-container {
width: 400px;
display: grid;
grid-template-columns: 200px 200px;
column-gap: 10px;
}
input {
margin-bottom: 10px;
padding: 12px;
border-radius: 4px;
border: 1px solid black;
display: block;
width: 100%;
}
.error {
margin: 0;
margin-bottom: 10px;
color: red;
font-size: 14px;
}
Listing 4-10 CSS styles for the Signup form
The form would look something like in Figure 4-3.
interface FormData {
firstName: string;
lastName: string;
password: string;
email: string;
}
Listing 4-11 FormData type for the useForm hook
I will then call the useForm hook and pass this type there as can be
seen in Listing 4-12.
Now I will spread the register function in all fields. Since I know
that the first name, email, and password fields are required, I would add
required:true in the register options object like in Listing 4-13.
<div className="form-container">
<div>
<input
{...register("firstName", { required: true
})}
placeholder="First name"
/>
</div>
<div>
<input {...register("lastName")}
placeholder="Last name" />
</div>
<div>
<input
{...register("email", {
required: true,
})}
placeholder="Email"
/>
</div>
<div>
<input
{...register("password", {
required: true,
})}
placeholder="Password"
/>
</div>
</div>
Listing 4-13 Spreading the register function in input fields
Now I would like to add pattern matching validation to the email field.
That can easily be done by passing a pattern property in the register
options param, which would have a regex as a value. If the corresponding
input value doesn’t match with that regex, then a validation error would be
added for the pattern.
The email regex would look something like the following as can be seen
in Listing 4-14.
/^[\w\-\.]+@([\w-]+\.)+[\w-]{2,4}$/
Listing 4-14 Regex pattern for an email
where
^[\w\-\.]+ would match the start of the string with word characters,
-, and . one or more times.
@ would match the @ character.
([\w-]+\.)+ would match word characters one or more times. \.
would match a dot.
[\w-]{2,4}$ would match the end of the string with word characters
between lengths 2 and 4.
I would pass this pattern in the pattern property of register options as
can be seen in Listing 4-15.
<input
{...register("email", {
required: true,
pattern: /^[\w-\.]+@([\w-]+\.)+[\w-]
{2,4}$/,
})}
placeholder="Email"
/>
Listing 4-15 Passing the pattern property in register options
<input
{...register("password", {
required: true,
minLength: 10,
})}
placeholder="Password"
/>
Listing 4-16 Passing the minLength property in register options
Let’s map the errors. With required I was mapping the required errors
only when type had a value required. Now for other error types like pattern,
I would show the errors only when type would have a value pattern and so
on. It would look something like Listing 4-17.
<div className="form-container">
<div>
<input
{...register("firstName", { required:
true })}
placeholder="First name"
/>
{errors.firstName?.type === "required"
&& (
<p className="error">First Name is
required</p>
)}
</div>
<div>
<input {...register("lastName")}
placeholder="Last name" />
</div>
<div>
<input
{...register("email", {
required: true,
pattern: /^[\w-\.]+@([\w-]+\.)+[\w-]
{2,4}$/,
})}
placeholder="Email"
/>
{errors.email?.type === "required" && (
<p className="error">Email is
required</p>
)}
{errors.email?.type === "pattern" && (
<p className="error">Email is
invalid</p>
)}
</div>
<div>
<input
{...register("password", {
required: true,
minLength: 10,
})}
placeholder="Password"
/>
{errors.password?.type === "required" &&
(
<p className="error">Password is
required</p>
)}
{errors.password?.type === "minLength"
&& (
<p className="error">Password should
have at least 10 characters</p>
)}
</div>
</div>
Listing 4-17 Mapping the errors
Now I would add the onSubmit functionality for the form. As
discussed initially, I will send the form data to a dummy endpoint /signup as
can be seen in Listing 4-18.
return (
<form onSubmit={handleSubmit(onSubmit)}>
Listing 4-18 Adding form submission logic
Now I would like to add some features to the submit button as described
initially. For that I would get the isDirty and isSubmitting values
from formState as can be seen in Listing 4-19.
const {
register,
handleSubmit,
formState: { errors, isDirty, isSubmitting },
} = useForm<FormData>();
Listing 4-19 Extracting isDirty and isSubmitting from formState
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-container">
<div>
<Controller
name="firstName"
control={control}
rules={{ required: true }}
render={({ field }) => (
<TextField
style={{ marginBottom: 10 }}
label="First name"
variant="outlined"
{...field}
/>
)}
/>
{errors.firstName?.type === "required"
&& (
<p className="error">First Name is
required</p>
)}
</div>
Listing 4-22 Integrating controlled components in React Hook Form
The form after replacing the first name input field with Material UI’s
TextField would look like Figure 4-4.
5. Validation
Usman Abdur Rehman1
(1) Islamabad, Pakistan
In the last chapter, I made two forms using React Hook Form, a BMI
calculator and a Signup form. During this process, you learned how to
integrate state with input fields, how to add basic validations, how to add
custom features like disabling the submit button when the form is
submitting, and finally how to integrate controlled components with React
Hook Form.
In this chapter, I will discuss validation in detail. I will demonstrate how
you can validate or revalidate forms at particular intervals (change, blur,
submit). I will also demonstrate how you can integrate third-party schema
solutions like Yup and Zod to validate your forms.
For styling, I will use the same stylesheet we used for the Signup form
in Chapter 4 since we will be using the same form for understanding
different validation patterns in this chapter. I will name this stylesheet
Validation.css and will be using it throughout this chapter.
<input
{...register("email", {
required: true,
pattern: /^[\w-\.]+@([\w-]+\.)+[\w-]
{2,4}$/,
})}
placeholder="Email"
/>
Listing 5-1 Validation using register options
<div>
<input
{...register("email", {
required: "Email is required",
pattern: {
value: /^[\w-\.]+@([\w-]+\.)+[\w-]
{2,4}$/,
message: "Email is invalid",
},
})}
placeholder="Email"
/>
<p className="error">
{errors?.email?.message}</p>
</div>
Listing 5-4 Validation using register options with a validation message
{
// function
validate: () => {},
// object of functions
validate: {
key1: () => {},
key2: () => {},
},
};
Listing 5-5 Types of values the validate property in register options can take
<input
{...register("email", {
required: "Email is required",
validate: (email) => {
if (/^[\w-\.]+@([\w-]+\.)+[\w-]
{2,4}$/.test(email)) {
return true;
}
return "Email is invalid";
},
})}
placeholder="Email"
/>
Listing 5-6 validate property with a function value
I used the regex’s test function in JavaScript to test if the email value
in the input matches that particular regex pattern. If that was the case, I
returned true; otherwise, I returned the error string.
The validate function can also be async. Let’s say I want to check,
by sending a call to a backend, if the email entered is valid or not. I can do
that by making validate an async function as can be seen in Listing 5-
7.
<input
{...register("email", {
required: "Email is required",
validate: async (email) => {
const isValid = await (
await fetch(`/isValidEmail?
email=${email}`)
).json();
return isValid || "Email is
invalid";
},
})}
placeholder="Email"
/>
Listing 5-7 validate property with an async function value
<input
{...register("email", {
validate: {
isRequired: (email) => {
return !!email || "Email is
required";
},
isEmailValid: (email) => {
if (/^[\w-\.]+@([\w-]+\.)+[\w-]
{2,4}$/.test(email)) {
return true;
}
return "Email is invalid";
},
},
})}
placeholder="Email"
/>
Listing 5-8 validate property with an object of functions value
Using this syntax, I was able to combine both validations, required and
pattern matching, in one validate object.
However, you saw that up until now I was only able to show one
validation error at a time. Sometimes you would want to show multiple
validation errors at once for a field. I will take a look at it in the next
section.
Validation Options
There are a lot of validation options that can be passed to the useForm
hook to change how validation is applied to the form fields. In this section,
I will cover those options.
mode/reValidateMode
Validation in React Hook Form is divided into two categories, validation
and revalidation. The validation that happens before the first form
submission is just called validation, while the validation that happens after
the first form submission is called revalidation.
Using the mode and reValidateMode params in the useForm
hook, you can change the validation strategy before and after form
submission (for validation and revalidation). By default, validation would
be triggered on the submit event, and revalidation would happen on
change events.
The mode param can have any of the values shown in Listing 5-9. The
default value for the mode param is onSubmit.
Based on what value you pass to the mode param, the validation would
be triggered at different stages of the form cycle. Table 5-1 would help you
understand that.
Table 5-1 Value Descriptions for the mode Param
Name Description
onSubmit Validation is triggered on the submit event.
onBlur Validation is triggered on the blur event.
onChange Validation is triggered on the change event.
onTouched Validation is initially triggered on the first blur event. After that, it is triggered on every
change event.
all Validation is triggered on both blur and change events.
Name Description
onSubmit Validation is triggered on the submit event.
onBlur Validation is triggered on the blur event.
Name Description
onChange Validation is triggered on the change event.
You can choose a combination of these params based on what your form
needs. If you value performance more, then maybe try to trigger validations
on blur and submit events. If you constantly want to perform validation
on every keystroke (change event) because that is what your form
demands, then you can trigger validation on the change event. And so on …
For a small form, the default values for these params are okay.
However, for larger forms, I would advise you to choose onBlur for the
reValidateMode param so that revalidation only occurs on blur
events.
criteriaMode
For the validation examples I have shown you, the errors object only
contains one error even if multiple validation errors are present for an input.
This can be controlled via the criteriaMode param.
The default value of criteriaMode param is firstError. This
means that React Hook Form would only return the first error based on its
validation order (random). If you want to change this behavior and want all
errors to be returned, then you would change its value to all.
When the criteriaMode param value is all, another property,
types, would be added to the corresponding field error object, which
would have the following shape as can be seen in Listing 5-11.
{
type: string;
ref: HTMLInputElement;
message: string;
types:{
[errorKey: string]: string
};
}
Listing 5-11 The shape for the types property inside the errors object
where the key would be a predefined error key like required and
minLength in case you are using those to add validations or the key in the
validate object if you are using the validate object for adding
validations. The value would be the error string you would specify in the
corresponding validation.
If now I want to show all the current errors of a field, for example, the
email field, it would look like the following as can be seen in Listing 5-12.
As you can see in this example, if you don’t care about the order in
which validation errors are shown, you can just map over them using
Object.values. However, if you do care about the order, then you can
create an array of errors yourself with your specified order and then map
that.
shouldFocusError
This param is true by default. If this param is true, upon form
submission, the first field that has a validation error would be focused,
which is a good UX. However, if you want to disable this behavior, you can
pass false in this param.
delayError
If you want that the errors are not displayed instantly when validation
happens, then you can pass the number of milliseconds by which you want
the error display to be delayed in this param.
Validation Using Schema
Validation can also be done by passing a schema defined using a third-party
library like Yup, Zod, Joi, etc. This is usually the preferred approach to do
validation as well, if you are already using Yup, Zod, etc., in your project to
validate something else. Using a schema to do validation would make sure
there is a standard approach being used to do validation. Let’s see how you
can integrate these libraries with React Hook Form.
First of all, in order to use schema validation, I would have to install a
package @hookform/resolvers using the installation command in
Listing 5-13.
After that I will import the resolver function for the corresponding
schema library from this package. I would use this to create a resolver from
the schema generated from the third-party library and pass it in the
resolver param of the useForm hook.
In this section, I will only discuss Yup and Zod, Yup because Yup is
already used as a schema validation library for Formik and Zod because it is
the most popular schema validation library. Now I will take the Signup
form from the last chapter and implement validation for that form using Yup
and Zod.
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="form-container">
<div>
<input {...register("firstName")}
placeholder="First name" />
{Object.values(errors?.firstName?.types
|| {})?.map((error) => (
<p className="error">{error}</p>
))}
</div>
<div>
<input {...register("lastName")}
placeholder="Last name" />
</div>
<div>
<input {...register("email")}
placeholder="Email" />
{Object.values(errors?.email?.types ||
{})?.map((error) => (
<p className="error">{error}</p>
))}
</div>
<div>
<input {...register("password")}
placeholder="Password" />
{Object.values(errors?.password?.types
|| {})?.map((error) => (
<p className="error">{error}</p>
))}
</div>
</div>
<button type="submit">Signup</button>
</form>
);
}
Listing 5-14 Schema validation using Yup
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormData>({
criteriaMode: "all",
resolver: zodResolver(schema),
});
Listing 5-15 Schema validation using Zod
Summary
In this section, we looked at validations in detail. I explained how you can
display custom validation messages, make custom validation functions
using the validate register option, change validation strategies using various
useForm params, and at the end perform validations using third-party
libraries like Zod and Yup.
In the next chapter, I will demonstrate how you can implement some
common form scenarios using React Hook Form.
OceanofPDF.com
© The Author(s), under exclusive license to APress Media, LLC, part of Springer Nature 2025
U. A. Rehman, Web Forms with React, Apress Pocket Guides
https://doi.org/10.1007/979-8-8688-1224-8_6
* {
box-sizing: border-box;
}
form {
width: 200px;
}
select,
input[type="text"] {
padding: 12px;
margin-bottom: 10px;
border-radius: 4px;
border: 1px solid black;
display: block;
width: 100%;
}
label {
font-size: 14px;
}
input[type="checkbox"] {
margin-bottom: 10px;
}
.error {
margin: 0;
margin-bottom: 10px;
color: red;
font-size: 14px;
}
.name-list-item {
display: flex;
gap: 6px;
margin-bottom: 10px;
}
interface FormData {
name: string;
}
return (
<div>
<input
{...register("name", { required: "Name is
required" })}
type="text"
placeholder="Name"
/>
<p className="error">{errors.name?.message}
</p>
</div>
);
};
export default function NestedForm() {
const form = useForm<FormData>();
const { handleSubmit } = form;
return (
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
<NestedInput />
<button type="submit">Signup</button>
</form>
</FormProvider>
);
}
Listing 6-2 Example of a deeply nested form using FormProvider/useFormContext
As you can see in Listing 6-2, NestedInput is a child of the
FormProvider component, and I was able to use the form methods and
state like register and errors inside the NestedInput without any
need for prop drilling from the parent to the child via the
useFormContext hook. This method can be very useful for creating
reusable fields, which I will be taking a look at in a later section.
Dependent Fields
In a previous chapter, I discussed the watch function. The watch function
can be used to watch for changes in any field. But the downside of this is
that the whole form will rerender whenever a particular field would change.
If you want that only a particular child component (an isolated
dependent field component) should rerender on some field change, then you
can use the useWatch hook.
The useWatch hook expects certain params, the most important of
which is name.
You can pass either a string or an array of strings (which corresponds to
field keys in the form) to the name param. Another param is the control
param. If you are using the FormProvider component, you don't need to
pass this control in the useWatch hook; otherwise, you will have to pass the
control you get from the useForm hook in the control param of the
useWatch hook.
In this example as can be seen in Listing 6-3, I have three fields, a text
field Name, a checkbox Is graduated?, and a specializations select field. I
only want to show the specializations select field if the Is graduated?
checkbox is checked. I can do that by extracting the specializations select
field (the dependent field) into a separate component and then, using the
useWatch hook, rendering that component if the particular checkbox is
checked (its corresponding value is true). Otherwise, I can return null.
It would look something like the following.
import {
FormProvider,
SubmitHandler,
useForm,
useFormContext,
useWatch,
} from "react-hook-form";
import "./Form.css";
interface FormData {
name: string;
isGraduated: boolean;
specialization: string;
}
return (
<select {...register("specialization")}>
{SPECIALIZATIONS.map((specialization) => (
<option value={specialization}>
{specialization}</option>
))}
</select>
);
};
return (
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register("name")}
placeholder="Name" type="text" />
<div>
<label>
<input {...register("isGraduated")}
type="checkbox" />
Is Graduated?
</label>
</div>
<SpecializationField />
<button type="submit">Submit</button>
</form>
</FormProvider>
);
}
Listing 6-3 Example of dependent fields using useWatch
As you can see in the SpecializationField component, I have
passed the field key of the checkbox, isGraduated, in the useWatch
hook, which gives me the value of that checkbox. If it is false, then I
return null, which means nothing would be rendered; otherwise, I would
render the specializations select field. If you would put a console.log
on the DependentFields component as well as in the
SpecializationField component, you will see that whenever the Is
graduated? checkbox would change, only the SpecializationField
component would rerender, not the whole form, which is a very performant
approach for making dependent fields.
Reusable Fields
In any application, there are a lot of fields/sub-forms and so on, which are
reusable across many forms, for example, the email and password fields.
For a general website, the email and password fields are present in the
Signin form, Signup form, Edit Profile form, etc. These two fields along
with their respective code regarding the register function and validations
can be made into a separate component known as Credentials, which
can then be reused in any form like Signin, Signup, etc. I will make use of
the FormProvider component so that I can extract the register
function and necessary logic right there in the Credentials component
using the useFormContext hook and the component doesn't require any
props for its usage. It would look something like the following.
import {
FormProvider,
SubmitHandler,
useForm,
useFormContext,
} from "react-hook-form";
import "./Form.css";
interface UserFormData {
firstName: string;
lastName: string;
bio: string;
age: number;
email: string;
password: string;
}
return (
<>
<div>
<input
{...register("email", {
required: true,
pattern: /^[\w-\.]+@([\w-]+\.)+[\w-]
{2,4}$/,
})}
placeholder="Email"
type="text"
/>
{errors.email?.type === "required" && (
<p className="error">Email is
required</p>
)}
{errors.email?.type === "pattern" && (
<p className="error">Email is
invalid</p>
)}
</div>
<div>
<input
{...register("password", {
required: true,
minLength: 10,
})}
placeholder="Password"
type="text"
/>
{errors.password?.type === "required" && (
<p className="error">Password is
required</p>
)}
{errors.password?.type === "minLength" &&
(
<p className="error">Password should
have at least 10 characters</p>
)}
</div>
</>
);
};
return (
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register("firstName")}
placeholder="First Name"
type="text"
/>
<input {...register("lastName")}
placeholder="Last Name" type="text" />
<Credentials />
<button type="submit">Submit</button>
</form>
</FormProvider>
);
}
return (
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
<Credentials />
<button type="submit">Submit</button>
</form>
</FormProvider>
);
}
return (
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register("firstName")}
placeholder="First Name"
type="text"
/>
<input {...register("lastName")}
placeholder="Last Name" type="text" />
<Credentials />
<textarea {...register("bio")}
placeholder="Bio" />
<input
{...register("age", { valueAsNumber:
true })}
placeholder="Age"
type="number"
/>
<button type="submit">Submit</button>
</form>
</FormProvider>
);
}
Listing 6-4 Example of reusable fields/forms using FormProvider/useFormContext
As you can see in Listing 6-4, I utilized the same principle I used in the
nested form example where I created a component Credentials where I
got the form-related accessories from the useFormContext hook and I
reused these fields very easily without the need to pass any props in three
forms (Signin, Signup, and Edit Profile) by wrapping them up in the
FormProvider component.
If you want to use any component from this example, you can import
them using named imports like import {ReusableProfile} from
… since I haven't used any default export in this example.
interface FormData {
name: string;
}
const Input = ({
name,
rules,
label,
}: {
name: string;
rules?: RegisterOptions;
label?: string;
}) => {
const { field } = useController({
name,
rules,
});
return (
<TextField
{...field}
inputRef={field.ref}
style={{ marginBottom: 10 }}
label={label}
/>
);
};
return (
<FormProvider {...form}>
<form onSubmit={handleSubmit(onSubmit)}>
<Input name="name" label="Name" rules={{
required: true }} />
{errors.name && <p>Name is required</p>}
<div />
<button type="submit">Submit</button>
</form>
</FormProvider>
);
};
Listing 6-5 Example of reusable controlled components using useController
interface FormData {
users: { name: string }[];
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
{fields.map((item, index) => (
<div key={item.id} className="name-list-
item">
<input
{...register(`users.${index}.name`)} type="text"
/>
Summary
In this chapter, I looked at some common form scenarios that developers
face while working on forms from reusable fields to nested forms,
dependent fields, mapping of multiple fields, etc. I then showed you how
React Hook Form makes it very convenient to work with these scenarios.
And that is it for this book. I hope this book helped you level up your
forms skills in React. I wish you the best of luck in your frontend career.
OceanofPDF.com
Index
A
async function
B
BMI calculator
CSS styles
error object
form
HTML skeleton
HTML structure
onSubmit function
react component
register function
required property
salient features
type property
useForm hook
valueAsNumber:true
values
C
Common form use cases
CSS styles
deeply nested form
dependent fields
mapping multiple fields
reusable controlled components
reusable fields
Controlled components
integrating
reusable
Credentials component
criteriaMode param
Cross-platform
D
Deeply nested form
defaultValues param
delayError param
DependentFields component
dev tools
E
Email regex
F
Form builder
Formik
FormProvider component
formState function
formState object
Form submission logic
G
GitHub stats
H, I, J, K, L
handleSubmit function
Handling validation
HTML5 validation
M
Material UI’s TextField
minLength
mode and reValidateMode params
MultipleFields function
N
Native form handling
handling validation
learning
library, advantages
robustness
scalability and reusability
standard
state handling
Native state handling
NestedInput
npm installs
O
onChange and onBlur functions
onSubmit function
onSubmit functionality
P, Q
Pull requests (PRs)
R
React form libraries
React Hook Form library
core
basic usage
useForm params
useForm returned values
using TypeScript
cross-platform
dev tools
form builder
vs. Formik
GitHub stats
npm installs
options
performance
subscriptions
type safety
useForm hook
validation
zero dependencies
Regex’s test function
register and handleSubmit functions
register function
reset function
Reusability
Reusable controlled components
Reusable fields
S
Scalability
setValue function (Boolean)
shouldFocusError param
Signup form
CSS styles
email regex
FormData type
HTML structure
isDirty and isSubmitting values
isSubmitting effect
layout
mapping errors
minimum length validation
onSubmit function
onSubmit functionality
pattern matching validation
register function
register options
salient features
SpecializationField component
T
TypeScript
U
useFieldArray hook
useFormContext hook
useForm hook
useForm params
defaultValues
values
useForm returned values
formState functions
handleSubmit functions
register functions
reset functions
watch functions
useWatch hook
V
Validate function
Validation patterns
criteriaMode param
delayError
mode and reValidateMode params
register options
register with validate function
register with validation and error message
schema validation using Yup
schema validation using Zod
shouldFocusError
validation options
validation using schema
Values param
W, X
watch function
Y
yupResolver
Z
zodResolver function
OceanofPDF.com