[React JS] Getting started with react-hook-form and Zod. For better form handling
Tags: react, frontend, webdev, beginners
Published: 2024-02-06 01:09:23

At the beginning of working with React, I had difficulty handling many inputs in a form with validation. I create a different state for every input element, so when there are more and more input elements, the number of states also increases, making it painful to maintain.

But now, all those difficulties are gone since I found [React Hook Form](https://react-hook-form.com/) and [zod](https://zod.dev/).

React Hook Form reduces the amount of code you need to write while removing unnecessary re-renders and it works really well with Zod. Zod is a TypeScript-first schema declaration and validation library.


### Preparation
Please take note that the code below is written in **typescript**.

All we need to do is just install these three packages:
- `npm install zod`
- `npm install react-hook-form`
- `npm install @hookform/resolvers`

to your React project.


### Let's dive into the code.
First of all, we need to import the packages mentioned above:

```react
import { useForm, SubmitHandler } from "react-hook-form";
import { z } from "zod";
import { zodResolver } from "@hookform/resolvers/zod";
```

Create schema and validation rules using Zod:

```react
const schema = z.object({
  name: z.string().min(1, { message: "Name is required." }),
  email: z
    .string()
    .min(1, { message: "Email is required." })
    .email({ message: "Please enter a valid email." }),
  password: z
    .string()
    .min(8, { message: "Password must be at least 8 characters." })
    .regex(
      /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$/,
      {
        message:
          "Password must contain at least one uppercase, one lowercase, one number, one special character",
      }
    ),
});
```
This is the beauty of Zod as it simplifies schema validation effortlessly. In the provided code, we define three attributes along with their respective validation rules, signifying that our form will encompass three distinct inputs.

Since we're using typescript, let's convert the schema above into ***type*** so we have typesafe for every part of our code moving on:

```react
type FormFields = z.infer<typeof schema>;
```

> By assigning `FormFields` to React Hook Form, we'll have beautiful typesafe
> ![Typesafe sample 1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k51sgiua9sqqb1iq6g6a.png)
> ![Typesafe sample 2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4qj94ltjghfrxcbaxhwt.png)


We've done with schema. Now, let's move on to React function component. In the previous steps, we already created `schema` and `FormFields` so we can attach them to React Hook Form:

```react
const {
  register,
  handleSubmit,
  setError,
  formState: { errors, isSubmitting },
} = useForm<FormFields>({
  resolver: zodResolver(schema),
});
```
`register`: In React Hook Form, the register function is a crucial method that allows you to register your input components with the form. By doing so, you inform the form about the existence of your input fields, enabling the library to manage their state and validation.

`handleSubmit`: The handleSubmit function in React Hook Form is a utility provided by the library to simplify the form submission process. It handles the form validation, prevents the default form submission behavior, and executes the provided callback function with the validated form data.

`setError`: A function used to manually set an error for a specific field. This can be useful in scenarios where you want to trigger or display a custom error message based on certain conditions.

`formState`: An object that provides information about the state of the form. It is typically used to access information such as whether the form is submitting, submitted, or if there are any validation errors, etc.

Here is the code:

```react
function Home() {
  const {
    register,
    handleSubmit,
    setError,
    formState: { errors, isSubmitting },
  } = useForm <
  FormFields >
  {
    resolver: zodResolver(schema),
  };

  // FUNCTION TO HANDLE FORM SUBMISSION
  const onSubmit: SubmitHandler<FormFields> = async (data) => {
    try {
      await new Promise((resolve) => setTimeout(resolve, 1000));

      // throw new Error("Email is already taken.");

      alert(JSON.stringify(data));
    } catch (error) {
      // SET ERROR MESSAGE IF ANY ERROR OCCURS
      setError("email", {
        message: "Email is already taken.",
      });
    }
  };

  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <h1 className="text-2xl mb-4">Register Form</h1>
      <form
        onSubmit={handleSubmit(onSubmit)}
        className="flex flex-col gap-2 w-96"
      >
        <input
          // REGISTER INPUT FIELD
          {...register("name")}
          className="border w-full rounded-md p-4"
          type="text"
          placeholder="Name"
        />
        {/* SHOW ERROR MESSAGE IF OCCURED */}
        {errors.name && (
          <span className="text-xs text-red-600 font-semibold">
            {errors.name.message}
          </span>
        )}
        <input
          // REGISTER INPUT FIELD
          {...register("email")}
          className="border w-full rounded-md p-4"
          type="text"
          placeholder="Email"
        />
        {/* SHOW ERROR MESSAGE IF OCCURED */}
        {errors.email && (
          <span className="text-xs text-red-600 font-semibold">
            {errors.email.message}
          </span>
        )}
        <input
          // REGISTER INPUT FIELD
          {...register("password")}
          className="border w-full rounded-md p-4"
          type="password"
          placeholder="Password"
        />
        {/* SHOW ERROR MESSAGE IF OCCURED */}
        {errors.password && (
          <span className="text-xs text-red-600 font-semibold">
            {errors.password.message}
          </span>
        )}

        <button
          type="submit"
          disabled={isSubmitting} // DISABLE BUTTON IF FORM IS SUBMITTING
          className="bg-blue-500 text-white p-2 rounded disabled:bg-gray-300 disabled:cursor-not-allowed"
        >
          {isSubmitting ? "Loading..." : "Register"}{" "}
          {/* SHOW LOADING TEXT IF FORM IS SUBMITTING */}
        </button>
      </form>
    </div>
  );
}

```

---

### Full code
{% embed https://gist.github.com/alfism1/7d325e300ab827c937eb980e1b65cd19 %}

---

That's all. No need to create a bunch of states anymore. If the inputs keep growing, we can just add them to the schema and let the React Hook Form + Zod do the magic.