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
相关产品推荐
相关产品推荐

