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

如何用React Hook Form和Zod实现两字段必填其一的正确校验

解决方案

要实现你需要的联动校验效果,需调整表单的校验触发逻辑,确保其中一个字段更新时同步触发另一个字段的校验,让错误提示同步消失。以下是具体修改方案:

关键修改点

  1. 字段联动触发校验:在slotNumber和slotSize的输入变更时,手动触发对方字段的校验,保证错误状态同步更新
  2. 清理残留错误:在Zod的校验逻辑中先清除两个字段的已有错误,避免状态残留
  3. 调整校验时机:设置表单在字段值变化时立即触发校验,无需等待失去焦点

修改后的完整代码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 12:07:49