如何用React Hook Form和Zod实现两字段必填其一的正确校验
解决方案
要实现你需要的联动校验效果,需调整表单的校验触发逻辑,确保其中一个字段更新时同步触发另一个字段的校验,让错误提示同步消失。以下是具体修改方案:
关键修改点
- 字段联动触发校验:在
slotNumber和slotSize的输入变更时,手动触发对方字段的校验,保证错误状态同步更新 - 清理残留错误:在Zod的校验逻辑中先清除两个字段的已有错误,避免状态残留
- 调整校验时机:设置表单在字段值变化时立即触发校验,无需等待失去焦点
修改后的完整代码
import { z } from 'zod'; import { useForm, Controller, SubmitHandler } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { Box, TextField, Button } from '@mui/material'; const formSchema = z .object({ description: z.string().nonempty('empty field'), slotNumber: z.string(), slotSize: z.string(), }) .superRefine(({ slotNumber, slotSize }, ctx) => { // 先清除两个字段的已有错误,避免状态残留 ctx.removeIssue({ path: ['slotNumber'] }); ctx.removeIssue({ path: ['slotSize'] }); // 仅当两个字段都为空时添加错误提示 if (slotNumber.trim() === '' && slotSize.trim() === '') { ctx.addIssue({ code: z.ZodIssueCode.custom, message: '必须填写slot size或slot number中的一个', path: ['slotSize'], }); ctx.addIssue({ code: z.ZodIssueCode.custom, message: '必须填写slot size或slot number中的一个', path: ['slotNumber'], }); } }); type FormFields = z.infer<typeof formSchema>; export default function App() { const { control, handleSubmit, formState: { errors }, reset, trigger, // 用于手动触发字段校验 } = useForm({ mode: 'all', reValidateMode: 'onChange', // 值变化时立即触发校验 resolver: zodResolver(formSchema), defaultValues: { description: '', slotNumber: '', slotSize: '', }, }); const onSubmit: SubmitHandler<FormFields> = (data) => { console.log(data); reset(); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <Box sx={{ display: 'flex', gap: '1rem', p: '1rem', flexDirection: 'column', }} > <Controller name="description" control={control} render={({ field }) => ( <TextField {...field} variant="outlined" error={!!errors.description} helperText={errors.description?.message} size="small" label="description" /> )} /> <Controller name="slotNumber" control={control} render={({ field }) => ( <TextField {...field} variant="outlined" error={!!errors.slotNumber} helperText={errors.slotNumber?.message} size="small" label="slot number" onChange={(e) => { field.onChange(e); // 同步触发slotSize的校验 trigger('slotSize'); }} /> )} /> <Controller name="slotSize" control={control} render={({ field }) => ( <TextField {...field} variant="outlined" error={!!errors.slotSize} helperText={errors.slotSize?.message} size="small" label="slot size" onChange={(e) => { field.onChange(e); // 同步触发slotNumber的校验 trigger('slotNumber'); }} /> )} /> <Button type="submit">Submit</Button> </Box> </form> ); }
效果说明
- 提交时若两个字段都为空,两者都会高亮显示错误提示
- 填写任意一个字段后,两个字段的错误提示会立即同步消失,无需再次点击提交按钮
内容的提问来源于stack exchange,提问作者Pavel Smirnov
相关产品推荐
相关产品推荐

