A very common feature of web applications is the ability to collect and process user input through forms. Forms are essential since they allow users to input data and interact with a website. However, building forms can start simple but can get tricky very quickly, especially when dealing with complex validation and state management.
In today’s email, we’ll take a look at some of the things to keep in mind when working with forms in React as well as understand the advantages that external libraries provide when it comes to building forms.
To go into a bit more detail, a form is a collection of input fields that allow a user to provide information to a web application. Forms typically consist of various input types, such as text fields, checkboxes, radio buttons, and dropdown menus. Once a user submits a form, a web application often processes the data before taking appropriate action (e.g. registering a new account, logging a user in, sending information to another party, etc.).
When building forms with React, there are a few things I always tend to try and keep in mind from both a development and user experience perspective.
State management: Building forms in React require managing the state of information kept within the form. Oftentimes, each input field needs to have its own state value which would need to be updated every time the user types or selects something.
Validation: Forms need to be validated to ensure that users provide correct and complete information. This involves checking that required fields are not empty, that input values are formatted correctly (e.g. an email address is formatted like an email), and that any other constraints are met (e.g. minimum and maximum values). Though it’s important to always have the server validate any input received from the client, having client-side validations can improve user experience and reduce the load on the server by catching errors before sending data.
Submission handling: When a user submits a valid form, the data needs to be sent to the server for processing. This typically involves preparing the state data to match what the server/API expects, then making an HTTP request, and finally handling the response. If the response errors, we need to notify the user and if the response is successful, we need to take the next appropriate action.
Handling the above is often easy when working with simple forms but when dealing with larger and more complicated forms, things can get difficult to manage fairly quickly. Third-party libraries exist to help with these tasks, such as Formik, React Hook Form, React Final Form, etc. These libraries have some differences between them but all provide capabilities to make form management and validation easier.
React Hook Form is often considered to be one of the most popular form management libraries in React and it helps simplify the process of building and validating forms. It provides a set of hooks that handle state management, validation, error handling, and submission handling, all with a minimal amount of code.
To use React Hook Form, you first need to install it via npm
or yarn
:
yarn add react-hook-form
Once installed, we can import the useForm()
hook from the library as follows:
import { useForm } from 'react-hook-form';
Let’s start a simple example of a React form that has three inputs - firstName
, lastName
, and email
.
import React from "react";
import "./index.css";
function App() {
return (
<div>
<form>
<div>
<label htmlFor="firstName">First Name</label>
<input name="firstName" placeholder="Hassan" />
</div>
<div>
<label htmlFor="lastName">Last Name</label>
<input name="lastName" placeholder="Djirdeh" />
</div>
<div>
<label htmlFor="email">Email</label>
<input
name="email"
placeholder="hassan.djirdeh@gmail.com"
type="email"
/>
</div>
<input type="submit" />
</form>
</div>
);
}
The above code will present a simple form of three input fields and a Submit
input button.
To leverage all the capabilities the React Form Hook library provides, we’ll need to import the useForm()
Hook and use it in our component.
import React from "react";
import { useForm } from "react-hook-form";
function App() {
const { register, handleSubmit, formState: { errors } } = useForm();
return (
<div>
/* form template */
</div>
);
}
The useForm()
hook returns an object that contains several properties and methods for managing form state and behavior. Above, we’re extracting the following properties:
register()
: used to register each input field with the form.handleSubmit()
: used to handle the form submission.formState.errors
: contains any errors within the form.We can use the register()
function to register each input field as follows:
import React from "react";
import { useForm } from "react-hook-form";
import "./index.css";
function App() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm();
return (
<div>
<form>
<div>
<label htmlFor="firstName">First Name</label>
<input name="firstName" placeholder="Hassan" {...register("firstName")} />
</div>
<div>
<label htmlFor="lastName">Last Name</label>
<input name="lastName" placeholder="Djirdeh" {...register("lastName")} />
</div>
<div>
<label htmlFor="email">Email</label>
<input
name="email"
placeholder="hassan.djirdeh@gmail.com"
type="email"
{...register("email")}
/>
</div>
<input type="submit" />
</form>
</div>
);
}
Within the register()
function, we’re also able to specify any input validation we would like. As an example, here is how we can validate that all input fields are required and that the value entered in the email
input field needs to be formatted as a valid email.
import React from "react";
import { useForm } from "react-hook-form";
import "./index.css";
function App() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm();
return (
<div>
<form>
<div>
<label htmlFor="firstName">First Name</label>
<input name="firstName" placeholder="Hassan" {...register("firstName", { required: true })} />
</div>
<div>
<label htmlFor="lastName">Last Name</label>
<input name="lastName" placeholder="Djirdeh" {...register("lastName", { required: true })} />
</div>
<div>
<label htmlFor="email">Email</label>
<input
name="email"
placeholder="hassan.djirdeh@gmail.com"
type="email"
{...register("email", {
required: true,
pattern: {
value: /\S+@\S+\.\S+/,
message: "Entered value does not match email format"
}
})}
/>
</div>
<input type="submit" />
</form>
</div>
);
}
We’re using the required
rule to ensure that the firstName
, lastName
, and email
fields are not left blank, and the pattern
rule to validate the format in the email
field.
The formState.errors
object contains any validation errors that are triggered during form submission. We can use the object to conditionally render an error message below each input under the condition the error message exists.
The one other change we’ll make is to have the form onSubmit()
event handler trigger the handleSubmit()
function available to us from the useForm()
Hook.
With these changes, our form in its final state will look like the following:
import React from "react";
import { useForm } from "react-hook-form";
import "./index.css";
function App() {
const {
register,
handleSubmit,
formState: { errors }
} = useForm();
const onSubmit = (data) => {
console.log("data", data);
};
return (
<div>
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="firstName">First Name</label>
<input
name="firstName"
placeholder="Hassan"
{...register("firstName", { required: true })}
/>
{errors.firstName && <p>This field is required</p>}
</div>
<div>
<label htmlFor="lastName">Last Name</label>
<input
name="lastName"
placeholder="Djirdeh"
{...register("lastName", { required: true })}
/>
{errors.lastName && <p>This field is required</p>}
</div>
<div>
<label htmlFor="email">Email</label>
<input
name="email"
placeholder="hassan.djirdeh@gmail.com"
type="email"
{...register("email", {
required: true,
pattern: {
value: /\S+@\S+\.\S+/,
message: "Entered value does not match email format"
}
})}
/>
{errors.email && (
<p>This field is required and needs to be a valid email</p>
)}
</div>
<input type="submit" />
</form>
</div>
);
}
We’re passing an onSubmit()
function that we’ve created as a callback to the handleSubmit()
function from our form hook. When the form is submitted successfully, our onSubmit()
function will simply console.log()
the data entered in our form.
With these changes, we’ll notice that form errors will appear if we attempt to submit the form without filling in the required information.
If the above doesn’t load, you can also see the above GIF in the following link.
If we fill in information for all the form fields but enter an invalid value in the email field, we’ll still be presented with the email field error message.
If the above doesn’t load, you can also see the above GIF in the following link.
If we fill in an appropriate email and click the Submit
button, we’ll then be presented with our form data in the console which tells us that our form was successfully submitted.
If the above doesn’t load, you can also see the above GIF in the following link.
Notice how we didn’t have to handle any form of state management and error validation through our own custom means? This is the main advantage of using a form library like React Hook Form — it takes care of the state management and validation for us.
You can interact with the above code example in the following CodeSandbox link.
Till next time! 🙂
— Hassan (@djirdehh)