DEV Community

Cover image for Leveraging Zod for Form Validation in React: Unleashing the Power of superRefine
Gleidson Leite da Silva
Gleidson Leite da Silva

Posted on

Leveraging Zod for Form Validation in React: Unleashing the Power of superRefine

Introduction: The Quest for Perfect Form Validation

Meet Lisa, a seasoned developer at EventPro, a company that creates software for managing events. One day, her manager approaches her with a new challenge: to build a sophisticated user registration form for their latest event management application. This form isn’t just a simple set of fields; it has complex validation rules that vary based on the user's input.
For instance, if the user is a VIP, additional fields like VIP code and extra information must be validated. However, handling such conditional logic within the form’s validation can quickly become a tangled mess, making the code hard to read and maintain. Lisa realizes that she needs a better way to handle these complex validation rules.

The Challenge: Validating Complex Forms

Lisa has encountered similar challenges before. Traditionally, she would use a combination of state management and conditional rendering to enforce validation rules. This often required creating multiple versions of the form schema, using hooks like useMemo to dynamically generate the schema based on user input. It was cumbersome, error-prone, and made the codebase difficult to maintain.

This time, Lisa decides to use Zod, a TypeScript-first schema declaration and validation library, which promises to simplify her task. She discovers that Zod’s powerful superRefine method can handle complex validation logic efficiently.

Seeting Up the Project

Lisa sets up a new React project and installs Zod:

npx create-react-app eventpro-registration
cd eventpro-registration
npm install zod

Enter fullscreen mode Exit fullscreen mode

Creating the Form Schema with Zod

Lisa defines a Zod schema to validate the form data. The schema uses superRefine to handle complex validation logic that depends on multiple fields.

import { z } from 'zod';

const isValidPhoneNumber = (number: string) => {
  return /^\d{10}$/.test(number);
};

export const registrationSchema = z
  .object({
    name: z.string().min(1, { message: 'Name is required' }),
    email: z.string().email({ message: 'Invalid email address' }),
    phoneNumber: z.string().refine(isValidPhoneNumber, {
      message: 'Invalid phone number',
    }),
    isVIP: z.boolean(),
    vipCode: z.string().optional(),
    additionalInfo: z.string().optional(),
  })
  .superRefine((data, ctx) => {
    if (data.isVIP) {
      if (!data.vipCode) {
        ctx.addIssue({
          path: ['vipCode'],
          code: z.ZodIssueCode.custom,
          message: 'VIP Code is required for VIPs',
        });
      }

      if (data.vipCode && data.vipCode.length !== 8) {
        ctx.addIssue({
          path: ['vipCode'],
          code: z.ZodIssueCode.custom,
          message: 'VIP Code must be exactly 8 characters long',
        });
      }

      if (!data.additionalInfo) {
        ctx.addIssue({
          path: ['additionalInfo'],
          code: z.ZodIssueCode.custom,
          message: 'Additional information is required for VIPs',
        });
      }
    }
  });
Enter fullscreen mode Exit fullscreen mode

Integrating the Schema with React

With the schema ready, Lisa integrates it with her React form using a custom hook for validation.

import React, { useState } from 'react';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { registrationSchema } from './validationSchema';

const RegistrationForm: React.FC = () => {
  const {
    register,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: zodResolver(registrationSchema),
  });

  const onSubmit = (data: any) => {
    console.log('Form Submitted', data);
  };

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label>Name:</label>
        <input {...register('name')} />
        {errors.name && <p>{errors.name.message}</p>}
      </div>
      <div>
        <label>Email:</label>
        <input {...register('email')} />
        {errors.email && <p>{errors.email.message}</p>}
      </div>
      <div>
        <label>Phone Number:</label>
        <input {...register('phoneNumber')} />
        {errors.phoneNumber && <p>{errors.phoneNumber.message}</p>}
      </div>
      <div>
        <label>
          <input type="checkbox" {...register('isVIP')} />
          VIP
        </label>
      </div>
      {errors.isVIP && <p>{errors.isVIP.message}</p>}
      <div>
        <label>VIP Code:</label>
        <input {...register('vipCode')} />
        {errors.vipCode && <p>{errors.vipCode.message}</p>}
      </div>
      <div>
        <label>Additional Information:</label>
        <textarea {...register('additionalInfo')} />
        {errors.additionalInfo && <p>{errors.additionalInfo.message}</p>}
      </div>
      <button type="submit">Register</button>
    </form>
  );
};

export default RegistrationForm;
Enter fullscreen mode Exit fullscreen mode

The Power of superRefine

with superRefine, Lisa can ensure that

  • The VIP code is required and exactly 8 characters long for VIP users.
  • Additional information is mandatory for VIP users.
  • The validation logic remains clean and centralized within the schema, making it easier to maintain and understand.

The Key Benefit: Eliminating Conditional Schema Logic

Previously, developers might have had to use complex conditional logic or dynamically generate new schemas with hooks like useMemo to handle scenarios where certain fields become required based on user input. This approach could be cumbersome and error-prone.

With Zod’s superRefine, Lisa avoids the need to generate new schemas dynamically. Instead, she can include all the necessary conditional validation logic directly within the schema. This approach simplifies the code and reduces the likelihood of bugs.

Benefits of Using Zod with superRefine

  1. Centralized Validation Logic: Keeping validation logic within the schema makes the code cleaner and easier to maintain.
  2. Complex Validation Made Simple: superRefine allows for complex validation rules that depend on multiple fields.
  3. Improved Developer Experience: By using TypeScript, Zod provides better type safety and editor support, reducing bugs and improving productivity.

Conclusion: Elevating Form Validation

By leveraging Zod and its superRefine method, Lisa was able to create a robust and flexible form validation system for EventPro's registration form. This approach not only made the code more maintainable but also improved the overall development experience. As a developer, adopting tools like Zod can significantly enhance your ability to handle complex validation scenarios with ease.

Happy coding, and may your forms always validate flawlessly!

Top comments (0)