Published on

Validating Dates with ZOD and React

Authors
  • Name
    Ripal & Zalak
    Twitter

How to Validate Start and End Dates Using Zod

Date validation is a common requirement in many applications. In this post, we’ll explore how to validate dates dynamically in React forms using Zod for schema validation. Specifically, we'll ensure:

  1. Start date cannot be in the past.
  2. End date must be after the start date.

Problem Statement

When building a vacation app, we need a form where users input:

  • Start date (must be in the future).
  • End date (must be after the start date).

Basic validation can be implemented in TypeScript using error handling, but we aim to enhance the user experience by dynamically validating the input in the form itself, triggered on onBlur events.

The Solution

We’ll use Zod for schema validation and React with react-hook-form for form handling. Below is the implementation.


Code Example

Step 1: Install Dependencies

Install the necessary libraries:

npm install zod react-hook-form @hookform/resolvers

Step 2: Create the Form Component

import React from 'react'
import { useForm } from 'react-hook-form'
import { z, ZodType } from 'zod'
import { zodResolver } from '@hookform/resolvers/zod'

function AddVacation(): JSX.Element {
  type FormData = {
    startDate: Date
    endDate: Date
  }

  // Define the schema
  const schema: ZodType<FormData> = z
    .object({
      startDate: z.coerce.date().refine((date) => date > new Date(), {
        message: 'Start date must be in the future',
      }),
      endDate: z.coerce.date(),
    })
    .refine((data) => data.endDate > data.startDate, {
      message: 'End date must be after start date',
      path: ['endDate'],
    })

  const {
    register,
    handleSubmit,
    trigger,
    formState: { errors },
  } = useForm<FormData>({ resolver: zodResolver(schema) })

  const onSubmit = (data: FormData) => {
    console.log('Form submitted:', data)
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <div>
        <label htmlFor="startDate">Start Date</label>
        <input type="date" {...register('startDate')} onBlur={() => trigger('startDate')} />
        {errors.startDate && <p>{errors.startDate.message}</p>}
      </div>

      <div>
        <label htmlFor="endDate">End Date</label>
        <input type="date" {...register('endDate')} onBlur={() => trigger('endDate')} />
        {errors.endDate && <p>{errors.endDate.message}</p>}
      </div>

      <button type="submit">Add Vacation</button>
    </form>
  )
}

export default AddVacation

FAQs

1. Why use zod.coerce.date()?

  • It converts strings (from input fields) to Date objects, ensuring type safety during validation.

2. What does .refine() do?

  • The .refine() method allows custom validation logic. In this example, it ensures the start date is in the future and the end date is after the start date.

3. Can I use Material-UI’s Date Picker?

  • Yes! Replace the <input type="date" /> with MUI’s <DatePicker /> and integrate it with react-hook-form using the Controller component.

Key Takeaways

  • Zod is a powerful tool for validating complex logic in forms.
  • Combining Zod with react-hook-form ensures a smooth user experience with instant feedback.
  • Custom validation logic can be added with .refine() to handle edge cases effectively.

Example Validation Cases

InputOutput
Start: Today, End: Tomorrow✅ Valid
Start: Yesterday, End: Tomorrow❌ Start date in the past
Start: Tomorrow, End: Today❌ End before start