DEV Community

Cover image for A Simple Documentation for using React Hook Form and Zod
Akshay Manoj
Akshay Manoj

Posted on

A Simple Documentation for using React Hook Form and Zod

Defining Types for the form

type FormFields = {
  email:string,
  password:string,
}
Enter fullscreen mode Exit fullscreen mode

Constructing a Form in React

<form className="tutorial gap-2 flex max-h-screen flex-col justify-center items-center">
        <input
          type="text"
          placeholder="Email"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
        />

        <input
          type="password"
          placeholder="Password"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"

        />
        <Button type="submit" >
         Submit
        </Button>
   </form>
Enter fullscreen mode Exit fullscreen mode

Install react-hook-form

npm i react-hook-form
Enter fullscreen mode Exit fullscreen mode

The Entities of React Hook Form

import { useForm } from "react-hook-form"

function App () {

        const {
        register, 
        handleSubmit, 
        setErrors, 
        formState: {errors, isSubmitting}
        } = useForm<FormFields>()

}       
Enter fullscreen mode Exit fullscreen mode

Using the register property to register values from the form

<form className="tutorial gap-2 flex max-h-screen flex-col justify-center items-center">
        <input
          type="text"
          placeholder="Email"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
            {...register("email")}
        />

        <input
          type="password"
          placeholder="Password"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
                    {...register("password")}
        />
        <Button type="submit" >
         Submit
        </Button>
   </form>
Enter fullscreen mode Exit fullscreen mode

Using the handleSubmit to handle the form submit.

πŸ’‘ We will define our custom submit function and pass that function as a parameter to handleSubmit from the onSubmit event handler inside the form

We use SubmitHandler to make TS understand that the submit function receives the form’s values and events

import { SubmitHandler, useForm } from "react-hook-form"

//writing the submit function
//this function mimics a submit
const onSubmit: SubmitHandler<FormFields> = async (data) =>{
        await new Promise((resolve) => setTimeout(resolve, 1000));
        console.log(data); // we will get data here once the form is submitted
}
Enter fullscreen mode Exit fullscreen mode
<form onSubmit={handleSubmit(onSubmit)} className="tutorial gap-2 flex max-h-screen flex-col justify-center items-center">
        <input
          type="text"
          placeholder="Email"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
            {...register("email")}
        />

        <input
          type="password"
          placeholder="Password"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
                    {...register("password")}
        />
        <Button type="submit" >
         Submit
        </Button>
   </form>
Enter fullscreen mode Exit fullscreen mode

Using Zod for validation and implementing errors

Installing dependencies

npm i zod
npm i @hookform/resolvers
Enter fullscreen mode Exit fullscreen mode

Defining zod Scehma and inferring the schema to existing Type Definition

import {z} from 'zod';
const schema = {
        email: z.string().email(),
        password: z.string().min(8)
}

/*
below given statement basically infers the above defined schema
to the data type defined before that is
type FormFields = {
  email:string,
  password:string,
}
*/
type FormFields = z.infer<typeof schema>
Enter fullscreen mode Exit fullscreen mode

Adding resolver to useForm hook to connect zod

import { zodResolver } from "@hookform/resolvers/zod";

function App () {

        const {
        register, 
        handleSubmit, 
        setErrors, 
        formState: {errors, isSubmitting}
        } = useForm<FormFields>(
        {
            resolver: zodResolver(schema)   
        }
        )

}       
Enter fullscreen mode Exit fullscreen mode

Intergrating the errors created by zod to the form using the errors of formState from useForm Hook

<form onSubmit={handleSubmit(onSubmit)} className="tutorial gap-2 flex max-h-screen flex-col justify-center items-center">
        <input
          type="text"
          placeholder="Email"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
            {...register("email")}
        />
        {errors.email && <div className="text-red-500">{errors.email.message}</div>}

        <input
          type="password"
          placeholder="Password"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
                    {...register("password")}
        />
                {errors.password && <div className="text-red-500">{errors.password.message}</div>}

        <Button type="submit" >
         Submit
        </Button>
   </form>
Enter fullscreen mode Exit fullscreen mode

Setting up default values to the form using defaultValues as parameter to the useForm Hook

πŸ’‘ These values will populate the form fields are provided.

import { zodResolver } from "@hookform/resolvers/zod";

function App () {

        const {
        register, 
        handleSubmit, 
        setErrors, 
        formState: {errors, isSubmitting}
        } = useForm<FormFields>(
        defaultValues:{
            email:"test@email.com"
        },
        {
            resolver: zodResolver(schema)   
        },
        )

}       
Enter fullscreen mode Exit fullscreen mode

Mocking an error inside the submit function and setting errors to the specific form fields

//writing the submit function
//this function mimics an Error
const onSubmit: SubmitHandler<FormFields> = async (data) =>{
        try{
            await new Promise((resolve) => setTimeout(resolve, 1000));
            throw new Error();
        }catch(error){
            setError('email',{message:"This is from a general point"})
            /*this function will give errors after clicking submit to 
            the email error message and show the messages under it*/
        }
        console.log(data); // we will get data here once the form is submitted
}
Enter fullscreen mode Exit fullscreen mode

Mocking an error inside the submit function and setting errors to the root field (General Errors)

πŸ’‘ adding root error set up under the form

<form onSubmit={handleSubmit(onSubmit)} className="tutorial gap-2 flex max-h-screen flex-col justify-center items-center">
        <input
          type="text"
          placeholder="Email"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
            {...register("email")}
        />
        {errors.email && <div className="text-red-500">{errors.email.message}</div>}

        <input
          type="password"
          placeholder="Password"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
                    {...register("password")}
        />
                {errors.password && <div className="text-red-500">{errors.password.message}</div>}

        <Button type="submit" >
         Submit
        </Button>
              {errors.root&& <div className="text-red-500">{errors.root.message}</div>}

   </form>
Enter fullscreen mode Exit fullscreen mode
//writing the submit function
//this function mimics an Error
const onSubmit: SubmitHandler<FormFields> = async (data) =>{
        try{
            await new Promise((resolve) => setTimeout(resolve, 1000));
            throw new Error();
        }catch(error){
            setError('root',{message:"This is from a general point"})
            /*this function will give errors after clicking submit to 
            the root error message and show the messages under it*/
        }
        console.log(data); // we will get data here once the form is submitted
}
Enter fullscreen mode Exit fullscreen mode

Using isSubmitting to showcase that the form is undergoing an async operation in the form.

<form onSubmit={handleSubmit(onSubmit)} className="tutorial gap-2 flex max-h-screen flex-col justify-center items-center">
        <input
          type="text"
          placeholder="Email"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
          {...register("email")}
        />
        {errors.email && <div className="text-red-500">{errors.email.message}</div>}

        <input
          type="password"
          placeholder="Password"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
          {...register("password")}

        />
        {errors.password && <div className="text-red-500">{errors.password.message}</div>}
        <Button type="submit" disabled={isSubmitting}>

          {isSubmitting ? "Loading" : "Submit"}
        </Button>
        {errors.root && <div className="text-red-500">{errors.root.message}</div>}

      </form><form onSubmit={handleSubmit(onSubmit)} className="tutorial gap-2 flex max-h-screen flex-col justify-center items-center">
        <input
          type="text"
          placeholder="Email"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
          {...register("email")}
        />
        {errors.email && <div className="text-red-500">{errors.email.message}</div>}

        <input
          type="password"
          placeholder="Password"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
          {...register("password")}

        />
        {errors.password && <div className="text-red-500">{errors.password.message}</div>}
        <Button type="submit" disabled={isSubmitting}>

          {isSubmitting ? "Loading" : "Submit"}
        </Button>
        {errors.root && <div className="text-red-500">{errors.root.message}</div>}

      </form>
Enter fullscreen mode Exit fullscreen mode

The complete Code

import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "./components/button"
import { SubmitHandler, useForm } from "react-hook-form"
import { z } from 'zod';

const schema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
})

type FormFields = z.infer<typeof schema>;

function App() {
  const {
    register,
    handleSubmit,
    setError,
    formState: { errors, isSubmitting }
  } = useForm<FormFields>({
    defaultValues: {
      email: "test@email.com",
    },
    resolver: zodResolver(schema),
  });

  const onSubmit: SubmitHandler<FormFields> = async (data) => {
    try {
      await new Promise((resolve) => setTimeout(resolve, 1000));
      throw new Error();
      console.log(data);
    }
    catch (error) {
      setError("root", { message: "This email is already taken" });
    }
  };

  return (
    <>
      <form onSubmit={handleSubmit(onSubmit)} className="tutorial gap-2 flex max-h-screen flex-col justify-center items-center">
        <input
          type="text"
          placeholder="Email"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
          {...register("email")}
        />
        {errors.email && <div className="text-red-500">{errors.email.message}</div>}

        <input
          type="password"
          placeholder="Password"
          className="border-2 border-slate-400 rounded-md text-black placeholder-black-50"
          {...register("password")}

        />
        {errors.password && <div className="text-red-500">{errors.password.message}</div>}
        <Button type="submit" disabled={isSubmitting}>

          {isSubmitting ? "Loading" : "Submit"}
        </Button>
        {errors.root && <div className="text-red-500">{errors.root.message}</div>}

      </form>

    </>

  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

Top comments (0)