How to Use Zod Validation for React Hook Forms with ShadCN's Form Component

How to Use Zod Validation for React Hook Forms with ShadCN's Form Component

Form validation in web applications is crucial to ensure the data being collected is accurate and meets the intended criteria. When working with React applications, using libraries like react-hook-form for form management, and Zod for schema validation can streamline this process significantly.

This article will guide you through integrating Zod validation with React Hook Forms, leveraging ShadCN's form component. By the end of this guide, you'll have a comprehensive understanding and practical knowledge to implement these technologies into your React project.

Getting Started

Before diving into the code, there are a few prerequisites and initial steps needed to set up the environment and install necessary packages.

Prerequisites

Ensure you have the following installed on your machine:

  • Node.js (v12 or higher)

  • npm or yarn

  • A React project set up (not covered in this guide)

Installation

To get started with using react-hook-form, Zod, and ShadCN's form component, you need to install the required packages. Open your terminal and run:

npm install react-hook-form zod @hookform/resolvers
npx shadcn@latest add form

This will install react-hook-form for form management, Zod for schema validation, and @hookform/resolvers to integrate Zod with react-hook-form. Additionally, it installs ShadCN's form component.

Defining Form Schemas with Zod

Zod is a powerful schema declaration and validation library. It ensures that the data you collect through forms meets the defined schema criteria. Here's how you can define schemas using Zod.

Example Schema

Let's create a simple schema for a form that collects a username. The username must be a string with a minimum length of 2 characters.

import { z } from 'zod';

const formSchema = z.object({
  username: z.string().min(2, { message: "Username must be at least 2 characters." }),
});

In this example, we define a schema formSchema where the username field must be a string with a minimum length of 2 characters. If the input does not meet these criteria, an error message will be displayed.

Building Forms Using ShadCN's Form Component

ShadCN provides a collection of composable components that help in building accessible and semantically correct forms. Let's break down the structure and components provided by ShadCN.

Form Structure

The basic structure of a form using ShadCN components is as follows:

<Form>
  <FormField control={...} name="..." render={() => (
    <FormItem>
      <FormLabel />
      <FormControl>{/* Your form field */}</FormControl>
      <FormDescription />
      <FormMessage />
    </FormItem>
  )} />
</Form>

In this structure:

  • <Form> wraps the entire form.

  • <FormField> represents a single field within the form.

  • <FormItem> contains all elements associated with a single form field, like label, input, description, and error message.

  • <FormLabel> defines the label for the input field.

  • <FormControl> wraps the input component.

  • <FormDescription> provides additional description or instructions for the field.

  • <FormMessage> displays validation or error messages.

Example Form Component

Here's a complete example of a form component that uses ShadCN's form component and integrates with react-hook-form and Zod validation.

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from '@shadcn/ui';

const formSchema = z.object({
  username: z.string().min(2, { message: "Username must be at least 2 characters." }),
});

export function ProfileForm() {
  const form = useForm({
    resolver: zodResolver(formSchema),
    defaultValues: { username: "" },
  });

  const onSubmit = (values) => {
    console.log(values);
  };

  return (
    <Form {...form}>
      <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
        <FormField control={form.control} name="username" render={({ field }) => (
          <FormItem>
            <FormLabel>Username</FormLabel>
            <FormControl>
              <input placeholder="shadcn" {...field} />
            </FormControl>
            <FormDescription>This is your public display name.</FormDescription>
            <FormMessage />
          </FormItem>
        )} />
        <button type="submit">Submit</button>
      </form>
    </Form>
  );
}

Integrating Zod with React Hook Form

In this section, we will dive into integrating the Zod schema with React Hook Form. This combination ensures that your forms are both easy to manage and thoroughly validated.

Setting Up React Hook Form

React Hook Form is a powerful library that simplifies form management in React applications. Using it with Zod allows you to define validation schemas that are both comprehensive and type-safe. Let's set up React Hook Form and integrate it with our Zod schema.

Creating the Form Component

We'll create a form component that uses the useForm hook from React Hook Form, and zodResolver to integrate the Zod schema.

Step-by-Step Integration
  1. Import Necessary Modules

    First, import the necessary modules from react-hook-form, zod, and @hookform/resolvers/zod.

    import { useForm } from 'react-hook-form';
    import { zodResolver } from '@hookform/resolvers/zod';
    import { z } from 'zod';
    import { Form, FormField, FormItem, FormLabel, FormControl, FormDescription, FormMessage } from '@shadcn/ui';
  2. Define your Schema

    Define the Zod schema as shown earlier.

    const formSchema = z.object({
      username: z.string().min(2, { message: "Username must be at least 2 characters." }),
    });
  3. Set Up useForm

    Use the useForm hook to set up the form with zodResolver for validation.

    const form = useForm({
      resolver: zodResolver(formSchema),
      defaultValues: { username: '' },
    });
  4. Handle Form Submission

    Define the onSubmit function to handle form submission.

    const onSubmit = (values) => {
      console.log(values);
    };
  5. Render the Form

    Use the ShadCN components to render the form.

    return (
      <Form {...form}>
        <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
          <FormField control={form.control} name="username" render={({ field }) => (
            <FormItem>
              <FormLabel>Username</FormLabel>
              <FormControl>
                <input placeholder="shadcn" {...field} />
              </FormControl>
              <FormDescription>This is your public display name.</FormDescription>
              <FormMessage />
            </FormItem>
          )} />
          <button type="submit">Submit</button>
        </form>
      </Form>
    );

Handling Server-Side Errors

Handling server-side errors is crucial for robust form validation. This involves capturing errors from the server and displaying them appropriately in your form.

Example: Handling Server-Side Validation

Let's extend our form to handle server-side validation errors.

  1. Define the API Endpoint

    Implement the server-side validation in an API route.

    import { UserSchema } from '@/types';
    import { NextResponse } from 'next/server';
    
    export async function POST(request: Request) {
      const body = await request.json();
      const result = UserSchema.safeParse(body);
    
      if (result.success) {
        return NextResponse.json({ success: true });
      }
    
      const serverErrors = Object.fromEntries(result.error?.issues.map(issue => [issue.path[0], issue.message]) || []);
      return NextResponse.json({ errors: serverErrors });
    }
  2. Integrate Server-Side Validation in the Form

    Modify the form component to handle server-side errors.

    import axios from 'axios';
    
    const onSubmit = async (data) => {
      try {
        const response = await axios.post('/api/form', data);
        const { errors } = response.data;
    
        Object.keys(errors).forEach((field) => {
          form.setError(field, { type: 'server', message: errors[field] });
        });
      } catch {
        alert('Submitting form failed!');
      }
    };

By following these steps, you create a robust form that handles both client-side and server-side validation effectively.

Common Pitfalls and Best Practices

While integrating Zod with React Hook Form and ShadCN's form component, you may encounter some challenges. Here are some common pitfalls and best practices to ensure a smooth implementation.

Common Pitfalls

  1. Validation Errors Not Displaying

    • Ensure that the keys used in the Zod schema match the names of the form fields.

    • Validate the schema definitions to ensure there are no typos or logical errors.

  2. Form Not Submitting

    • Check if there are any JavaScript errors in the console that might indicate issues with form handling or submission.

    • Ensure that the submit button is within the form element and correctly configured.

Best Practices

  1. Type Safety with TypeScript

    • Use TypeScript to define schemas and form data types. This ensures type safety and reduces runtime errors.

    type FormData = z.infer<typeof formSchema>;
  2. Accessibility Considerations

    • Ensure all form fields are accessible by using proper ARIA attributes and labels. ShadCN's components help in managing accessibility effectively.

    <FormLabel htmlFor="username">Username</FormLabel>
    <FormControl>
      <input id="username" {...field} />
    </FormControl>
  3. Consistent Error Handling

    • Handle errors consistently by displaying user-friendly error messages. Utilize the <FormMessage> component from ShadCN for this purpose.

  4. Server-Side Validation

    • Always complement client-side validation with server-side validation to ensure data integrity.

  5. Reusable Components

    • Create reusable form components to maintain consistency and reduce code duplication.

Conclusion

Integrating Zod validation with React Hook Forms and ShadCN's form component allows you to build robust, accessible, and type-safe forms in your React applications. By following the steps outlined in this guide, you can ensure that your forms are well-validated and user-friendly. Don't forget to handle both client-side and server-side validations to maintain data integrity and provide a seamless user experience.

For more information, you can explore the following resources:

Raymond Yeh

Raymond Yeh

Published on 17 November 2024

Get engineers' time back from marketing!

Don't let managing a blog on your site get in the way of your core product.

Wisp empowers your marketing team to create and manage content on your website without consuming more engineering hours.

Get started in few lines of codes.

Choosing a CMS
Related Posts
How to Use Zod Validation for React Hook Forms

How to Use Zod Validation for React Hook Forms

Discover how to seamlessly integrate Zod validation with React Hook Form, ensuring robust, type-safe validation for your forms. A step-by-step guide from setup to advanced techniques!

Read Full Story
Yup Validation for React Forms: A Complete Guide

Yup Validation for React Forms: A Complete Guide

Simplify React form validation with Yup! Learn why it matters, how to implement it with Formik or React Hook Form, and tackle common challenges with ease using our expert tips.

Read Full Story
Validating API Response with Zod

Validating API Response with Zod

Learn why validating API responses with Zod is indispensable for TypeScript apps, especially when handling unexpected data formats from third-party APIs in production.

Read Full Story