Published on

Working with Multiple Checkboxes in Next.js 14 Using Zod, ShadCN/UI, and React-Hook-Form

Authors
  • Name
    Ripal & Zalak
    Twitter

Introduction

Handling multiple checkboxes in forms can be tricky, especially when working with tools like Next.js 14, Zod, ShadCN/UI, and React-Hook-Form. Whether you’re building a reservation system or a survey form, the combination of these libraries can simplify form handling and validation. This blog provides a detailed approach to manage multiple checkboxes effectively.

Why Use React-Hook-Form with Zod and ShadCN/UI?

  1. React-Hook-Form: Lightweight and performant form library.
  2. Zod: Schema validation with type safety.
  3. ShadCN/UI: Provides pre-styled UI components, including checkboxes.

Setting Up the Environment

Before diving into code, ensure you have the following installed:

  • Next.js 14
  • React-Hook-Form
  • Zod
  • ShadCN/UI
npm install next react-hook-form @hookform/resolvers zod shadcn-ui

Creating a Multi-Checkbox Form

1. Define the Zod Schema

The schema ensures the selected checkboxes are validated correctly.

import { z } from 'zod'

export const formSchema = z.object({
  reservationMethod: z.array(z.string()).min(1, 'Select at least one option.'),
})

2. Build the Form Component

Use React-Hook-Form to manage form state and validation.

import { useForm, Controller } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Checkbox } from '@/components/ui/checkbox';
import { formSchema } from '@/schemas/formSchema';

const ReservationForm = () => {
  const {
    control,
    handleSubmit,
    formState: { errors },
  } = useForm({
    resolver: zodResolver(formSchema),
    defaultValues: {
      reservationMethod: [],
    },
  });

  const onSubmit = (data: any) => {
    console.log(data);
  };

  const options = [
    { id: 'tourOperator', label: 'Tour Operator' },
    { id: 'internet', label: 'Internet' },
    { id: 'resort', label: 'Directly with the Resort' },
    { id: 'travelAgent', label: 'Travel Agent' },
  ];

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <h2>Select Reservation Methods</h2>

      <div className="flex flex-col gap-4">
        {options.map((option) => (
          <Controller
            key={option.id}
            name="reservationMethod"
            control={control}
            render={({ field }) => (
              <div className="flex items-center gap-2">
                <Checkbox
                  id={option.id}
                  checked={field.value.includes(option.label)}
                  onCheckedChange={(checked) => {
                    const updatedValues = checked
                      ? [...field.value, option.label]
                      : field.value.filter((value) => value !== option.label);

                    field.onChange(updatedValues);
                  }}
                />
                <label htmlFor={option.id}>{option.label}</label>
              </div>
            )}
          />
        ))}

        {errors.reservationMethod && (
          <p className="text-red-500">{errors.reservationMethod.message}</p>
        )}
      </div>

      <button type="submit" className="mt-4 bg-blue-500 text-white py-2 px-4">
        Submit
      </button>
    </form>
  );
};

export default ReservationForm;

3. Add Styling

ShadCN/UI provides a robust set of components. Ensure your CSS is configured to handle the UI components properly.

/* Example CSS */
.checkbox {
  appearance: none;
  width: 20px;
  height: 20px;
  border: 2px solid #ccc;
  border-radius: 4px;
  display: inline-block;
  cursor: pointer;
}
.checkbox:checked {
  background-color: #4a90e2;
  border-color: #4a90e2;
}

Handling Additional Fields

If you need dynamic fields based on checkbox selection, React-Hook-Form makes it easy. For instance:

{options.map((option) => (
  <Controller
    key={option.id}
    name="reservationMethod"
    control={control}
    render={({ field }) => (
      <div className="flex items-center gap-2">
        <Checkbox
          id={option.id}
          checked={field.value.includes(option.label)}
          onCheckedChange={(checked) => {
            const updatedValues = checked
              ? [...field.value, option.label]
              : field.value.filter((value) => value !== option.label);

            field.onChange(updatedValues);
          }}
        />
        <label htmlFor={option.id}>{option.label}</label>

        {option.id === 'travelAgent' && field.value.includes(option.label) && (
          <input
            type="text"
            placeholder="Name of agency"
            className="border p-2"
          />
        )}
      </div>
    )}
  />
))}

Conclusion

Combining Next.js 14, Zod, ShadCN/UI, and React-Hook-Form simplifies form handling and validation. By following the steps above, you can build dynamic, type-safe forms with ease.