A Simple Documentation for using React Hook Form and Zod
Akshay Manoj
Posted on August 6, 2024
Defining Types for the form
type FormFields = {
email:string,
password:string,
}
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>
Install react-hook-form
npm i react-hook-form
The Entities of React Hook Form
import { useForm } from "react-hook-form"
function App () {
const {
register,
handleSubmit,
setErrors,
formState: {errors, isSubmitting}
} = useForm<FormFields>()
}
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>
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
}
<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>
Using Zod for validation and implementing errors
Installing dependencies
npm i zod
npm i @hookform/resolvers
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>
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)
}
)
}
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>
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)
},
)
}
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
}
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>
//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
}
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>
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
💖 💪 🙅 🚩
Akshay Manoj
Posted on August 6, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.