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

NextJS 14+Zod+React Hook Form:新增/编辑表单必填字段处理难题

解决NextJS 14表单必填验证与受控组件警告问题

问题背景

使用NextJS 14、Zod和基于React-Hook-Form的ShadCN UI开发支持新增/编辑的表单时,遇到两个冲突问题:

  • 给必填字段设空字符串默认值,Zod会将空字符串判定为有效值,绕过必填验证;下拉选择框的必填验证难以处理,且不想给Zod Schema添加.optional()破坏结构简洁性。
  • 将必填字段默认值改为undefined后,必填验证生效,但输入数据时触发React警告:
    Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen.
    

解决方案

核心思路是用空字符串作为所有字段的初始值,通过Zod的nonempty()方法确保空字符串不通过必填验证,同时保证组件从初始化开始就是受控状态,避免警告。

1. 调整Zod Schema,用nonempty()强化必填验证

对需要必填的字段(如type、line1),将单纯的z.string()替换为z.string().nonempty(),该方法会直接拦截空字符串,比.min(1)语义更清晰,且能适配下拉选择框的场景。

修改后的Zod Schema:

export const AddressFormSchema = z.object({
  form_type: z.string().optional(),
  id: z.string().optional(),
  // 新增nonempty()确保非空,支持自定义提示
  type: z.string().nonempty({ message: "请选择地址类型" }),
  line1: z.string().trim().toLowerCase().nonempty({ message: "请填写地址第一行" }),
  line2: z.string().trim().toLowerCase().optional(),
  city: z.string().trim().toLowerCase().optional(),
  state: z.string().optional(),
  postal_code: z
    .union([
      z.string().length(0, {
        message: "不能超过10位",
      }),
      z.string().trim().max(10),
    ])
    .optional(),
  agency_id: z.string().optional(),
  contact_id: z.string().optional(),
  supplier_id: z.string().optional(),
  created_at: z.string().optional(),
  updated_at: z.string().optional(),
});

2. 统一设置默认值为空字符串

将所有字段的初始值设为空字符串,编辑场景下若有数据则替换,无数据则保留空字符串。这样组件从初始化就是受控状态,不会出现undefined到非undefined的切换,彻底消除警告。

修改后的Form Page代码:

const { update } = props;

const defaultValues = {
  form_type: update ? "update" : "create",
  id: update ? props.address?.id : "",
  type: update ? props.address?.type || "" : "",
  line1: update ? props.address?.line1 || "" : "",
  line2: update ? props.address?.line2 || "" : "",
  city: update ? props.address?.city || "" : "",
  state: update ? props.address?.state || "" : "",
  postal_code: update ? props.address?.postal_code || "" : "",
};

const form = useForm<AddressFormSchemaType>({
  resolver: zodResolver(AddressFormSchema),
  defaultValues: defaultValues,
});

补充说明

  • 下拉选择框需保留默认空选项(如<option value="">请选择</option>),此时未选择时type字段值为空字符串,会被Zod的nonempty()拦截,触发必填提示。
  • 对于line1这类带.trim()的字段,即使用户输入纯空格,trim后为空字符串,同样会被nonempty()拦截,符合业务需求。

内容的提问来源于stack exchange,提问作者Mike Varela

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 16:30:07