typescript - การทำงานกับหลายกล่องเลือกใน Nextjs14, zod, shadcn/ui และ react-hook-form
ฉันกำลังสร้างแบบฟอร์มให้ลูกค้า และหนึ่งในคำถามต้องการให้ผู้ใช้เลือกช่องทำเครื่องหมาย (หรือหลายช่องทำเครื่องหมาย) ฉันยังไม่เข้าใจโครงสร้างของ zod อย่างเต็มที่ ดังนั้นฉันจึงติดอยู่กับการจะทำสิ่งนี้ ฉันลองเปลี่ยนโครงสร้างหลายครั้ง ฉันแม้กระทั่งลองทำการแมปผ่านอาร์เรย์ของรายการที่ฉันต้องการให้ถูกเลือกและแสดงเป็นช่องทำเครื่องหมาย แต่ก็ไม่สำเร็จ
ฉันก็พยายามที่จะกำหนดสตริงให้กับกล่องเลือกด้วยเช่นกัน (ฉันไม่ได้ใช้มันสำหรับคำถามถูกหรือผิด)
นี่คือตารางฟอร์มของฉัน:
<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>
)}
/>
สคีมาของฉันสำหรับฟิลด์เฉพาะนั้น: reservationMethod: z.string().array(),
คำตอบ #1
ฉันลองทำแบบนี้แล้วมันได้ผลสำหรับฉัน
ใช้รายการช่องทำเครื่องหมายเป็นคู่คีย์ค่าออบเจ็กต์
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;
คำตอบ #2
อีกวิธีหนึ่งในการใช้ส่วนประกอบ chadcn ui กับ react-hook-form ที่ฉันพบว่าง่ายคือการใช้ control.
const schema = z.object({
title: z.string().min(5, { message: 'Must be 5 or more characters long' }),
type: z.string(),
....}
สมมติว่าคุณต้องการใช้ type กับ checkbox คุณจะเริ่มด้วยอันนี้
const {
register,
handleSubmit,
control,
setValue,
formState: { errors },
} = useForm<JobIForm>({
resolver: zodResolver(schema),
mode: 'onSubmit',
});
แล้วคุณก็ให้สร้างองค์ประกอบฟอร์มแบบนี้
<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>
