Level Up Your React Forms with React Hook Form and Zod Validation
Introduction
Form validation is crucial for any web application. While React offers various form libraries, React Hook Form stands out for its performance and ease of use. Combining it with Zod, a TypeScript-first schema validation library, gives you type-safe and robust validation.
Why React Hook Form and Zod?
- React Hook Form: Minimal re-renders, easy to integrate, and performant.
- Zod: TypeScript-first, declarative schema definition, and excellent error handling.
Setting up the Project
First, create a new React project (if you don't have one already):
npx create-react-app my-form-app
cd my-form-app
Then, install the necessary packages:
npm install react-hook-form zod @hookform/resolvers
Defining the Schema with Zod
Let's define a simple form schema for a user registration form:
import * as z from "zod";
const userSchema = z.object({
firstName: z.string().min(2, { message: "First name must be at least 2 characters." }),
lastName: z.string().min(2, { message: "Last name must be at least 2 characters." }),
email: z.string().email({ message: "Invalid email address." }),
password: z.string().min(8, { message: "Password must be at least 8 characters." }),
confirmPassword: z.string(),
}).refine((data) => data.password === data.confirmPassword, {
message: "Passwords don't match",
path: ["confirmPassword"], // path of error
});
export type UserSchemaType = z.infer<typeof userSchema>;
This schema defines the structure of our form data and specifies validation rules for each field. The refine
method adds a custom validation to ensure the password and confirm password fields match.
Implementing the Form with React Hook Form
Now, let's create the React component using React Hook Form and the Zod resolver:
import React from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import * as z from "zod";
import {userSchema, UserSchemaType} from './schema';
const RegistrationForm = () => {
const { register, handleSubmit, formState: { errors } } = useForm<UserSchemaType>({
resolver: zodResolver(userSchema),
defaultValues: {
firstName: "",
lastName: "",
email: "",
password: "",
confirmPassword: "",
}
});
const onSubmit = (data: UserSchemaType) => {
console.log("Form Data:", data);
alert(JSON.stringify(data)); // Replace with your actual submission logic
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label htmlFor="firstName">First Name:</label>
<input type="text" id="firstName" {...register("firstName")} />
{errors.firstName && <p>{errors.firstName.message}</p>}
</div>
<div>
<label htmlFor="lastName">Last Name:</label>
<input type="text" id="lastName" {...register("lastName")} />
{errors.lastName && <p>{errors.lastName.message}</p>}
</div>
<div>
<label htmlFor="email">Email:</label>
<input type="email" id="email" {...register("email")} />
{errors.email && <p>{errors.email.message}</p>}
</div>
<div>
<label htmlFor="password">Password:</label>
<input type="password" id="password" {...register("password")} />
{errors.password && <p>{errors.password.message}</p>}
</div>
<div>
<label htmlFor="confirmPassword">Confirm Password:</label>
<input type="password" id="confirmPassword" {...register("confirmPassword")} />
{errors.confirmPassword && <p>{errors.confirmPassword.message}</p>}
</div>
<button type="submit">Register</button>
</form>
);
};
export default RegistrationForm;
Key points:
useForm
hook initializes the form and provides methods for managing form state and validation.zodResolver
connects React Hook Form with our Zod schema.register
function is used to register input fields and connect them to the form state.formState.errors
contains validation errors from Zod.
Conclusion
React Hook Form and Zod provide a powerful combination for building robust and type-safe forms in React. This approach streamlines form validation, improves code quality, and enhances the user experience.