How to Validate a Date Range Picker with Zod in TypeScript Projects
Tags: typescript, zod, shadcnui, tutorial
Published: 2024-04-14 06:04:14

### What is Zod?

> [**Zod** is a TypeScript-first schema declaration and validation library.](https://zod.dev)

### Prerequisites

I used shadcn/ui for the date range picker component, date-fns for the date format, and zod for the validation.

- [shadcn/ui](https://ui.shadcn.com/)
- [date-fns](https://date-fns.org/)
- [zod](https://zod.dev/)

### Getting started

Creating challenges on the [Lotus project](https://github.com/onurhan1337/lotus) requires attention to detail, especially when setting the start and end dates. To simplify this process for others, I wrote an blog about creating a date range selection component for the challenge creation form.

First, I use Zod to create a new schema called **dateRangeSchema**. This schema has a date range object with the start date and end date of the form data.

```ts
import { z } from "zod";
export const dateRangeSchema = z
  .object({
    // other fields
    dateRange: z.object(
      {
        from: z.date(),
        to: z.date(),
      },
      {
        required_error: "Please select a date range",
      }
    ),
  })
  .refine((data) => data.dateRange.from < data.dateRange.to, {
    path: ["dateRange"],
    message: "From date must be before to date",
  });
```

Now that the Zod schema is ready, I will create the form step by step, starting with the necessary imports.

I have added the dateRangeSchema content to **@/zod/schemas/date-range.ts**. You can save it in the file if you want but don't forget to edit it if necessary.

```ts
import { cn } from "@/lib/utils";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { format } from "date-fns";
import { CalendarIcon } from "lucide-react";
import {
  Form,
  FormControl,
  FormDescription,
  FormField,
  FormItem,
  FormLabel,
  FormMessage,
} from "../ui/form";
import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
import { dateRangeSchema } from "@/zod/schemas/date-range";
```

Schema and imports are done. Now I can create the component. When I create the component, I first define defaultValues and resolver using **useForm**.

- **defaultValues** is a property that sets the initial values of the form fields when the form is first loaded or reset.
- **zodResolver** is a function that converts a Zod schema into a form resolver, ensuring that the form data matches the schema before submission.

```ts
export const Component = () => {
  const form = useForm({
    defaultValues: {
      dateRange: {
        from: new Date(),
        to: new Date(),
      },
    },
    resolver: zodResolver(dateRangeSchema),
  });

  const onSubmit = (data: z.infer<typeof dateRangeSchema>) => {
    console.log(data);
  };

  return (
    // ...
  );
};
```

Now everything is ready. Finally, I include the date range picker in `Component` using `shadcn/ui` forms.

```ts
return (
  <Form {...form}>
    <form onSubmit={form.handleSubmit(onSubmit)}>
      <FormField
        control={form.control}
        name="dateRange"
        render={({ field }) => (
          <FormItem className="flex flex-col">
            <FormLabel>Start and End Date</FormLabel>
            <Popover modal={true}>
              <PopoverTrigger asChild>
                <Button
                  id="date"
                  variant="outline"
                  className={cn(
                    "w-full justify-start text-left font-normal",
                    !field.value.from && "text-muted-foreground"
                  )}
                >
                  <CalendarIcon className="mr-2 h-4 w-4" />
                  {field.value.from ? (
                    field.value.to ? (
                      <>
                        {format(field.value.from, "LLL dd, y")} -{" "}
                        {format(field.value.to, "LLL dd, y")}
                      </>
                    ) : (
                      format(field.value.from, "LLL dd, y")
                    )
                  ) : (
                    <span>Pick a date</span>
                  )}
                </Button>
              </PopoverTrigger>
              <PopoverContent className="w-auto p-0" align="center">
                <Calendar
                  initialFocus
                  mode="range"
                  defaultMonth={field.value.from}
                  selected={{
                    from: field.value.from!,
                    to: field.value.to,
                  }}
                  onSelect={field.onChange}
                  numberOfMonths={2}
                />
              </PopoverContent>
            </Popover>
            <FormDescription>Select the start and end date</FormDescription>
            <FormMessage />
          </FormItem>
        )}
      />
    </form>
  </Form>
);
```

Now we have everything ready. To make the code better,

- You can make Date Range Picker a component.
- You can improve the **onSubmit** functionality of the form by using the [Sonner](https://sonner.emilkowal.ski/) package.
- You can add custom error messages to the schema.

follow me on [X](https://x.com/onurhan1337).