I'm using React Hook Form with shadcn UI components and zod for validation in my React project. I have a list of services where each checkbox represents a service, and I want to store the selected service IDs in an array field (serviceDetails.services). When a checkbox is checked, I want to add the service ID to the array; when unchecked, I want to remove it.
Here is my current code using shadcn's Checkbox component:
//constants.ts
const SERVICES = ["fitness", "sauna", "cardio"] as const;
//schema.ts
const serviceDetailsSchema = z.object({
services: z.array(z.enum(SERVICES)),
totalAmount: z.number().min(0, "Total amount must be non-negative"),
});
export const memberEntrySchema = z.object({
serviceDetails: serviceDetailsSchema,
});
//types.ts
export type MemberEntryFormData = z.infer<typeof memberEntrySchema>;
//MemberEntryForm.tsx
const MemberEntryForm = () => {
const services = [
{
id: 1,
name: "Fitness",
price: 1000,
},
{
id: 2,
name: "Sauna",
price: 500,
},
{
id: 3,
name: "Cardio",
price: 800,
},
];
const methods = useForm<MemberEntryFormData>({
resolver: zodResolver(memberEntrySchema),
defaultValues: {
//other forms default values aswell
serviceDetails: {
services: [],
totalAmount: 0,
},
},
});
const onSubmit = (data: MemberEntryFormData) => {
console.log(data);
};
return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)} className="space-y-6">
//other forms aswell
<ServiceDetailsForm services={services} />
</form>
</FormProvider>
)
//ServiceDetailsForm.tsx
type ServiceDetailsFormProps = {
services: {
id: number;
name: string;
price: number;
}[];
};
const ServiceDetailsForm = ({ services }: ServiceDetailsFormProps) => {
const { control, watch } = useFormContext<MemberEntryFormData>();
const servicesValue = watch("serviceDetails.services");
console.log(servicesValue);
const items = services.map((service) => ({
id: service.name + " - " + service.price + "/month",
label: service.name + " - " + service.price + "/month",
}));
return (
<div className="space-y-2">
<Heading3>Service Details</Heading3>
<FormField
control={control}
name="serviceDetails.services"
render={() => (
<FormItem>
<div className="mb-4">
<FormLabel className="text-base">Sidebar</FormLabel>
</div>
{items.map((item, index) => (
<FormField
key={item.id}
control={control}
name={`serviceDetails.services`}
render={({ field }) => {
return (
<FormItem
key={item.id}
className="flex flex-row items-start space-x-3 space-y-0"
>
<FormControl>
<Checkbox
checked={field.value?.includes(item.id)}
onCheckedChange={(checked) => {
const updatedValues = Array.isArray(field.value)
? field.value
: [];
if (checked) {
field.onChange([...updatedValues, item.id]);
} else {
field.onChange(
updatedValues.filter(
(value) => value !== item.id
)
);
}
}}
/>
</FormControl>
<FormLabel className="font-normal">
{item.label}
</FormLabel>
</FormItem>
);
}}
/>
))}
<FormMessage />
</FormItem>
)}
/>
//other form fields
</div>
);
};
I'm encountering an "undefined" error when submitting the form. Here's the error.
Please let me know what I am missing here.
I was able to get rid of the undefined error. The first thing I did was simplifying the id for the items
variable so it matches your SERVICES
constant.
const items = services.map((service) => ({
id: service.name.toLowerCase() as typeof SERVICES[number], // Change this line
label: service.name + " - " + service.price + "/month",
}));
Then in your Checkbox component, I changed the checked attribute to this, so it matches the item's id correctly:
<Checkbox
checked={servicesValue?.includes(item.id as typeof SERVICES[number])}
I'm not sure if this is what you're looking for but it does get rid of the undefined error for me.