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)