Building Robust Multi-Step Forms with React Hook Form and Zod
Introduction
When it comes to building complex, multi-step forms in React applications, managing state, validation, and user experience can become a daunting task. React Hook Form (RHF) and Zod are two powerful libraries that can help simplify this process. In this article, we'll explore how to leverage the strengths of both libraries to create robust, user-friendly multi-step forms.
Setting Up the Project
To get started, create a new React project using your preferred method (e.g., Create React App). Then, install the required dependencies:
npm install react-hook-form zod
Basic Form Setup with React Hook Form
React Hook Form provides an efficient way to manage form state and validation. Here's a basic example of how to set up a form using RHF:
import { useForm } from 'react-hook-form'; function MyForm() { const { register, handleSubmit, errors } = useForm(); const onSubmit = async (data) => { // Handle form submission console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <label> First Name: <input type="text" {...register('firstName')} /> {errors.firstName && <div>{errors.firstName.message}</div>} </label> <button type="submit">Submit</button> </form> ); }
Adding Validation with Zod
Zod is a TypeScript-first schema validation library that allows you to define robust validation schemas. To integrate Zod with React Hook Form, you'll need to create a validation schema using Zod's z object:
import { z } from 'zod'; const userSchema = z.object({ firstName: z.string().min(2, 'First name must be at least 2 characters'), lastName: z.string().min(2, 'Last name must be at least 2 characters'), });
Then, pass the schema to the useForm hook using the resolver option:
import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; const userSchema = z.object({ firstName: z.string().min(2, 'First name must be at least 2 characters'), lastName: z.string().min(2, 'Last name must be at least 2 characters'), }); function MyForm() { const { register, handleSubmit, errors } = useForm({ resolver: zodResolver(userSchema), }); const onSubmit = async (data) => { // Handle form submission console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <label> First Name: <input type="text" {...register('firstName')} /> {errors.firstName && <div>{errors.firstName.message}</div>} </label> <label> Last Name: <input type="text" {...register('lastName')} /> {errors.lastName && <div>{errors.lastName.message}</div>} </label> <button type="submit">Submit</button> </form> ); }
Building Multi-Step Forms
To create a multi-step form, you'll need to manage the form's state and navigation between steps. One approach is to use a state machine to control the form's flow. Here's an example:
import { useState } from 'react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; const userSchema = z.object({ firstName: z.string().min(2, 'First name must be at least 2 characters'), lastName: z.string().min(2, 'Last name must be at least 2 characters'), email: z.string().email('Invalid email address'), }); const steps = [ { id: 1, label: 'Personal Details' }, { id: 2, label: 'Contact Information' }, ]; function MyForm() { const [currentStep, setCurrentStep] = useState(1); const { register, handleSubmit, errors } = useForm({ resolver: zodResolver(userSchema), }); const onSubmit = async (data) => { // Handle form submission console.log(data); }; const handleNextStep = () => { if (currentStep < steps.length) { setCurrentStep(currentStep + 1); } }; const handlePrevStep = () => { if (currentStep > 1) { setCurrentStep(currentStep - 1); } }; return ( <form onSubmit={handleSubmit(onSubmit)}> {currentStep === 1 && ( <div> <label> First Name: <input type="text" {...register('firstName')} /> {errors.firstName && <div>{errors.firstName.message}</div>} </label> <label> Last Name: <input type="text" {...register('lastName')} /> {errors.lastName && <div>{errors.lastName.message}</div>} </label> </div> )} {currentStep === 2 && ( <div> <label> Email: <input type="email" {...register('email')} /> {errors.email && <div>{errors.email.message}</div>} </label> </div> )} <button type="button" onClick={handlePrevStep}> Previous </button> <button type="button" onClick={handleNextStep}> Next </button> {currentStep === steps.length && ( <button type="submit">Submit</button> )} </form> ); }
Conclusion
In this article, we've demonstrated how to build robust multi-step forms using React Hook Form and Zod. By combining the strengths of both libraries, you can create seamless and user-friendly forms that simplify complex user interactions. Remember to leverage the power of Zod's validation schemas to ensure robust data validation, and use React Hook Form's efficient state management to streamline your form-building process. With these tools, you'll be well-equipped to tackle even the most complex form-building challenges in your React applications.