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

