DEV Community

Cover image for [React JS] Getting started with react-hook-form and Zod. For better form handling
Alfi Samudro Mulyo
Alfi Samudro Mulyo

Posted on • Updated on

[React JS] Getting started with react-hook-form and Zod. For better form handling

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 and zod.

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:

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

Create schema and validation rules using Zod:

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",
      }
    ),
});
Enter fullscreen mode Exit fullscreen mode

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:

type FormFields = z.infer<typeof schema>;
Enter fullscreen mode Exit fullscreen mode

By assigning FormFields to React Hook Form, we'll have beautiful typesafe
Typesafe sample 1
Typesafe sample 2

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:

const {
  register,
  handleSubmit,
  setError,
  formState: { errors, isSubmitting },
} = useForm<FormFields>({
  resolver: zodResolver(schema),
});
Enter fullscreen mode Exit fullscreen mode

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:

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

Full code


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.

Top comments (0)