You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

useFieldArray+Zod+AsyncSelect表单验证出现undefined/类型错误

使用useFieldArray、Shadcn/ui、AsyncSelect与Zod时的表单验证问题解决方案

问题根源分析

  1. 未选择选项时错误提示显示undefined:
    • Zod Schema未对location数组配置自定义非空提示,且初始值未设为空数组,导致未选择时location为undefined,Zod抛出默认"Required"错误,但错误提示未正确绑定到数组层面的错误信息。
  2. 设置location.${index}.label后类型不匹配:
    • 将FormField的name绑定到单个label字段后,react-hook-form期望该字段为字符串,但AsyncSelect选中的是包含label和value的对象,赋值时触发类型不匹配错误;同时破坏了useFieldArray对数组对象的管理逻辑。

解决方案

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 18:43:22