useFieldArray+Zod+AsyncSelect表单验证出现undefined/类型错误
使用useFieldArray、Shadcn/ui、AsyncSelect与Zod时的表单验证问题解决方案
问题根源分析
- 未选择选项时错误提示显示undefined:
- Zod Schema未对
location数组配置自定义非空提示,且初始值未设为空数组,导致未选择时location为undefined,Zod抛出默认"Required"错误,但错误提示未正确绑定到数组层面的错误信息。
- Zod Schema未对
- 设置
location.${index}.label后类型不匹配:- 将FormField的name绑定到单个
label字段后,react-hook-form期望该字段为字符串,但AsyncSelect选中的是包含label和value的对象,赋值时触发类型不匹配错误;同时破坏了useFieldArray对数组对象的管理逻辑。
- 将FormField的name绑定到单个
解决方案
1. 修正Zod Schema
确保location数组的非空验证、元素结构验证,并设置默认空数组避免初始undefined:
import { z } from "zod"; const FormSchema = z.object({ location: z.array( z.object({ label: z.string().min(1, "地点名称不能为空"), value: z.string().min(1, "地点值不能为空") }) ) .min(1, "至少需要一个地点") // 自定义数组非空提示 .default([]) // 初始化为空数组,避免undefined });
2. 修正useFieldArray与FormField的绑定逻辑
绑定整个数组项对象,而非单个label字段,确保AsyncSelect的value类型与Zod定义匹配:
import { useForm, useFieldArray, zodResolver } from "react-hook-form"; import { AsyncSelect } from "@/components/ui/async-select"; import { FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { FormSchema } from "./your-schema-path"; const YourForm = () => { const { control, formState: { errors } } = useForm({ resolver: zodResolver(FormSchema), defaultValues: { location: [] // 与Schema默认值保持一致 } }); const { fields, append, remove } = useFieldArray({ control, name: "location" // 绑定到整个数组 }); // 异步加载地点选项的示例方法 const loadLocations = async (inputValue: string) => { // 替换为你的异步请求逻辑 const res = await fetch(`/api/locations?query=${inputValue}`); return res.json(); }; return ( <form> {/* 渲染所有地点字段 */} {fields.map((field, index) => ( <FormItem key={field.id} className="mb-4"> <FormLabel>地点 {index + 1}</FormLabel> <FormField control={control} name={`location.${index}`} // 绑定整个数组项对象 render={({ field }) => ( <AsyncSelect {...field} placeholder="选择地点" loadOptions={loadLocations} getOptionLabel={(option) => option.label} getOptionValue={(option) => option.value} /> )} /> {/* 先显示数组层面的错误,再显示单个项的错误 */} <FormMessage> {errors.location?.message || errors.location?.[index]?.label?.message} </FormMessage> <button type="button" onClick={() => remove(index)} className="mt-2 text-sm text-red-600" > 删除 </button> </FormItem> ))} {/* 添加地点按钮 */} <button type="button" onClick={() => append({ label: "", value: "" })} className="mb-4 px-4 py-2 bg-blue-500 text-white rounded" > 添加地点 </button> {/* 提交按钮 */} <button type="submit" className="px-4 py-2 bg-green-500 text-white rounded"> 提交 </button> </form> ); }; export default YourForm;
3. 错误提示逻辑说明
- 数组层面的错误(
至少需要一个地点)会通过errors.location?.message获取,当未添加任何地点或所有地点被删除时触发。 - 单个地点项的错误(
地点名称不能为空)通过errors.location?.[index]?.label?.message获取,当选中的地点对象不符合Zod验证时触发。
内容的提问来源于stack exchange,提问作者MirroX
相关产品推荐
相关产品推荐

