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

React Hook Form中条件渲染字段的错误聚焦功能不稳定问题排查

React Hook Form中条件渲染字段的错误聚焦功能不稳定问题排查

我之前也碰到过React Hook Form里条件渲染字段聚焦异常的问题,结合你的代码来看,主要是验证逻辑和状态清理没做好,咱们一步步来解决这个问题:

一、修正表单验证Schema(核心问题)

你当前的Schema要求所有三个字段(tillNumber、paybillNumber、accountNumber)都必填,但实际上用户只会选择其中一种支付方式,填写对应的一组字段。这就导致切换支付方式后,未渲染的字段仍然会被标记为错误,React Hook Form会尝试聚焦一个不存在的DOM元素,进而导致当前渲染的字段无法正常聚焦。

所以要改用z.discriminatedUnion来实现基于支付方式的条件验证:

const FormSchema = z.discriminatedUnion("modeOfPayment", [
  z.object({
    modeOfPayment: z.literal("Mpesa Buy Goods"),
    tillNumber: z.string()
      .nonempty({ message: "Please provide a value" })
      .min(5, { message: "Please enter a valid till number" })
      .max(8, { message: "Please enter a valid till number" }),
  }),
  z.object({
    modeOfPayment: z.literal("Mpesa Paybill"),
    paybillNumber: z.string()
      .nonempty({ message: "Please provide a value" })
      .max(8, { message: "Please enter a valid paybill number" }),
    accountNumber: z.string()
      .nonempty({ message: "Please provide a value" })
      .max(20, { message: "Please enter a valid account number" }),
  }),
]);

二、切换支付方式时清理无关字段状态

当用户切换支付方式时,需要清空之前选中方式对应的字段值和错误状态,避免残留的状态干扰当前表单的验证和聚焦逻辑:

const handleModeOfPaymentChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
  const newValue = e.target.value;
  setSelectedPaymentMode(newValue);
  
  // 清空无关字段的值与错误
  if (newValue === "Mpesa Buy Goods") {
    setValue("paybillNumber", "");
    setValue("accountNumber", "");
    clearErrors(["paybillNumber", "accountNumber"]);
  } else if (newValue === "Mpesa Paybill") {
    setValue("tillNumber", "");
    clearErrors(["tillNumber"]);
  }
};

三、优化错误聚焦逻辑

你之前用watch监听字段值来触发聚焦的时机不对,应该在表单提交失败后,根据当前选中的支付方式,找到第一个有错误的字段并手动聚焦:

useEffect(() => {
  // 仅在表单提交失败且已选择支付方式时处理聚焦
  if (formState.isSubmitFailed && selectedPaymentMode) {
    let targetField = "";
    
    if (selectedPaymentMode === "Mpesa Buy Goods") {
      targetField = "tillNumber";
    } else if (selectedPaymentMode === "Mpesa Paybill") {
      // 优先检查paybillNumber,再检查accountNumber
      targetField = errors.paybillNumber ? "paybillNumber" : "accountNumber";
    }
    
    // 如果目标字段存在错误,触发聚焦
    if (targetField && errors[targetField]) {
      setFocus(targetField);
    }
  }
}, [formState.isSubmitFailed, selectedPaymentMode, errors, setFocus]);

四、简化Ref的使用

你不需要手动维护自己的ref变量,React Hook Form的register返回的ref已经足够让setFocus方法找到对应的DOM元素,所以可以去掉自定义的ref:

// 移除这些自定义ref
// const tillNumberRef = useRef<HTMLInputElement | null>(null);
// const paybillNumberRef = useRef<HTMLInputElement | null>(null);
// const accountNumberRef = useRef<HTMLInputElement | null>(null);

// 保留RHF的register返回的ref即可
const { ref: tillNoRef, ...restTillNumber } = register("tillNumber");
const { ref: paybillNoRef, ...restPaybillNumber } = register("paybillNumber");
const { ref: accountNoRef, ...restAccountNumber } = register("accountNumber");

然后在input组件里直接使用:

<input
  {...restTillNumber}
  ref={tillNoRef} // 仅使用RHF的ref
  type="text"
  className={`h-8 w-11/12 self-center text-sm ${
    errors?.tillNumber?.message
      ? "border-red-700 focus:border-none focus:ring focus:ring-red-700 focus:ring-2"
      : "focus:border-none focus:ring focus:ring-emerald-700 focus:ring-2"
  }`}
/>

这样调整后,不管用户怎么切换支付方式,提交空表单时第一个错误字段都会正常聚焦,而且验证逻辑也更符合实际业务场景。

备注:内容来源于stack exchange,提问作者Dave Odipo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.21 13:53:03