Aidan Mc Intosh
Aidan Mc Intosh

Reputation: 243

Working with multiple checkboxes in Nextjs14, zod, shadcn/ui and react-hook-form

I'm building a form for a client and one of the questions want the user to select a checkbox (or multiple checkboxes). I haven't fully grasped zod's schema so I'm a little stuck on how to implement this. I tried changing around the schema a couple times, I even mapped through an array of the items I wanted to be checked and rendered them as checkboxes but I was unsuccessful.

I'm also trying to assign string to the checkboxes (i'm not using them for true and false questions).

Here's my form field:

<FormField
    name="reservationMethod"
    control={form.control}
    render={({ field }) => (
        <FormItem className="flex flex-col gap-4">
            <FormLabel className="text-base md:text-lg text-black font-extralight">
                Which of the following did you use to make your
                reservation?
            </FormLabel>
            <FormControl>
                <div className="flex gap-4">
                    <div className="flex items-center gap-1">
                        <Checkbox
                            id="tourOperator"
                            value="Tour Operator"
                            checked={field.value.includes("Tour Operator")}
                            onCheckedChange={() =>
                                handleCheckboxChange("Tour Operator")
                            }
                        />
                        <Label htmlFor="tourOperator">Tour Operator</Label>
                    </div>

                    <div className="flex items-center gap-1">
                        <Checkbox
                            id="internet"
                            value="Internet"
                            checked={field.value.includes("Internet")}
                            onCheckedChange={() =>
                                handleCheckboxChange("Internet")
                            }
                        />
                        <Label htmlFor="internet">Internet</Label>
                    </div>

                    <div className="flex items-center gap-1">
                        <Checkbox
                            id="directlyWithResort"
                            value="Directly with the resort"
                            checked={field.value.includes(
                                "Directly with the resort"
                            )}
                            onCheckedChange={() =>
                                handleCheckboxChange("Directly with the resort")
                            }
                        />
                        <Label htmlFor="directlyWithResort">
                            Directly with the resort
                        </Label>
                    </div>

                    <div className="flex items-center gap-1">
                        <Checkbox
                            id="travelAgent"
                            value="Travel Agent"
                            checked={field.value.includes("Travel Agent")}
                            onCheckedChange={() =>
                                handleCheckboxChange("Travel Agent")
                            }
                        />
                        <Label htmlFor="travelAgent">Travel Agent</Label>
                    </div>

                    <div className="flex justify-center items-center ml-10">
                        <Label>Name of agency: </Label>
                        <Input type="text" {...field} />
                    </div>
                </div>
            </FormControl>
        </FormItem>
    )}
/>

My schema for that specific field: reservationMethod: z.string().array(),

Upvotes: 2

Views: 10042

Answers (2)

Kiran Kumar K
Kiran Kumar K

Reputation: 11

I tried to do like this and thats worked for me

use checkbox lists as object key value pairs

import {
  Form,
  FormControl,
  FormField,
  FormLabel,
  FormMessage,
  FormItem,
  FormDescription,
} from "@/components/ui/form";
import { Checkbox } from "@/components/ui/checkbox";
type UnexpectedObject = {
  [key: string]: any;
};
type Props = {
  form: any;
  fieldControlName: any;
  fieldLabel: any;
  checkboxItems: UnexpectedObject;
};

const MultipleCheckboxWithLabel = ({
  form,
  fieldControlName,
  fieldLabel,
  checkboxItems,
}: Props) => {
  return (
    <FormField
      control={form.control}
      name={fieldControlName}
      render={() => (
        <FormItem className="">
          <div className="mb-4">
            <FormLabel className="text-base">{fieldLabel}</FormLabel>
          </div>
          {Object.entries(checkboxItems).map(([key, value], _idx) => (
            <FormField
              key={_idx}
              control={form.control}
              name={fieldControlName}
              render={({ field }) => {
                return (
                  <FormItem
                    key={key}
                    className="flex flex-row items-start space-x-3 space-y-0"
                  >
                    <FormControl>
                      <Checkbox
                        checked={field.value?.includes(key)}
                        onCheckedChange={(checked) => {
                          return checked
                            ? field.onChange([...field.value, key])
                            : field.onChange(
                                field.value?.filter(
                                  (value: any) => value !== key
                                )
                              );
                        }}
                      />
                    </FormControl>
                    <FormLabel className="font-normal">{value}</FormLabel>
                  </FormItem>
                );
              }}
            />
          ))}
          <FormMessage />
        </FormItem>
      )}
    />
  );
};

export default MultipleCheckboxWithLabel;

Upvotes: 0

AIMABLE
AIMABLE

Reputation: 1075

Another way of using chadcn ui component with react-hook-form that I find easy is to use control.

const schema = z.object({
 title: z.string().min(5, { message: 'Must be 5 or more characters long' }),
 type: z.string(),
 ....}

Let's say you want to use type with checkbox you would start with this

const {
    register,
    handleSubmit,
    control,
    setValue,
    formState: { errors },
  } = useForm<JobIForm>({
    resolver: zodResolver(schema),
    mode: 'onSubmit',
  });

Then have you form component created like this

<div className='flex items-center justify-start flex-wrap gap-2'>
     <div className='flex flex-col gap-4 grow-[1] shrink-1'>
         <Label htmlFor='title'>Contract Type</Label>
             <Controller
                control={control}
                name='type'
                render={({ field: { onChange, value } }) => (
                  <Select onValueChange={onChange} value={value}>
                    <SelectTrigger>
                      <SelectValue placeholder='Choose contact type' />
                    </SelectTrigger>
                    <SelectContent>
                      <SelectGroup>
                        <SelectLabel>Contract Type</SelectLabel>
                        {types.map((type, index) => (
                          <SelectItem value={type} key={`key-${index}`}>
                            {type}
                          </SelectItem>
                        ))}
                      </SelectGroup>
                    </SelectContent>
                  </Select>
                )}
              />

              {errors.type?.message !== undefined ? (
                <p className='text-sm text-pink-600'>{errors.type.message}</p>
              ) : (
                <p className='text-sm text-muted-foreground'>
                  This endicates the type of contract this job is for
                </p>
              )}
            </div>

Upvotes: 0

Related Questions