我正在使用 NextJS 14、Zod 和 ShadCN UI,它使用 React-Hook-Form。
我正在创建一个可用于“创建”和“更新”的表单。我将值传递到表单中,并使用“创建”表单的那些或空字符串设置 defaultValues。但是,由于某些字段是强制性的,因此空字符串仍然是一个值,并且会绕过正常表单字段验证。在文本输入上,我可以添加 .min(1, {message: 'some message'}) 但我还有一个下拉菜单,我正在努力强制执行强制值。理想情况下,我可以保持 ZOD 模式干净,无需添加 .optional(),并且表单将为我管理必要的字段。
最近我决定将必填字段的默认值设置为“未定义”,这解决了我的必填字段问题,但是当将数据输入到这些字段之一时,我收到一条错误消息,让我知道表单正在从不受控制转变为受控制- 由于“未定义”作为起始值。
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 架构 ---
export const AddressFormSchema = z.object({
form_type: z.string().optional(),
id: z.string().optional(),
type: z.string(),
line1: z.string().trim().toLowerCase(),
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: "No more than 10 digits",
}),
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(),
});
-表格页---
const { update } = props;
const defaultValues = {
form_type: update ? "update" : "create",
id: update ? props.address?.id : undefined,
type: update ? props.address?.type : undefined,
line1: update ? props.address?.line1 : undefined,
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,
});
可用于
create
和 update
的表单可以有条件地呈现特定于操作的表单字段,并且还可以添加自定义错误。
为了实现此目的,您可以使用
discriminatedUnion
和 merge
这是一个示例架构
// common fields for "create" and "update"
const BaseAddressSchema = z.object({
street: z.string().min(3).max(255),
city: z.string().min(3).max(255),
state: z.string().min(3).max(255),
});
const AddressSchema = z.discriminatedUnion("form_type", [
z
.object({
form_type: z.literal("create"),
supplier_id: z.string().min(3).max(255), // validation only applied when "create" value selected in dropdown
})
.merge(BaseAddressSchema),
z
.object({
form_type: z.literal("update"),
// here you can add "update" specific fields
})
.merge(BaseAddressSchema),
]);
// in order to add validation to the dropdown eg(no default value present and value must be selected) you can add here
const CustomAddressSchema = z
.object({
form_type: z.string().min(1, "Form Type is required"),
})
.and(AddressSchema);
在组件中你可以这样
const {
register,
watch,
handleSubmit,
formState: { errors },
} = useForm<any>({
resolver: zodResolver(CustomAddressSchema ),
defaultValues: defaultValues,
});
const form_type_value = watch("form_type");
...
// conditionally render a certain field based on the dropdown
{form_type_value == "create" && (
<div>
<label className="mr-2">Supplier id</label>
<input
className="border-[1px] border-black"
type="text"
{...register("supplier_id")}
placeholder="supplier Id"
/>
{/* @ts-ignore */}
<p>{errors.supplier_id && errors?.supplier_id?.message}</p>
</div>
)}
这是一个工作复制品https://replit.com/@MubashirWaheed/zodDiscrimulatedUnion#app/page.tsx