为什么 react-hook-form 的 isValid 为什么有时在表单验证通过没有 errors 的情况下会出现 false?
在 React Hook Form 中,formState.isValid 为 true 表示表单当前完全有效 (无任何验证错误),但即使 errors 对象为空({}),isValid 有时仍会出现 false 的情况,这是库的正常行为或常见配置导致的。以下是主要原因及解释(基于官方文档和社区常见问题):
1. 初始状态默认 false
-
表单 mount 时,
isValid初始值为false。 -
库不会自动验证
defaultValues,这是为了可访问性(accessibility)考虑,避免初始加载时就显示错误。 -
即使所有字段初始值有效,且
errors为空,isValid仍需等待首次验证触发后才可能变为true。 -
解决 :在组件 mount 后手动调用
trigger()来验证初始值:tsxconst { trigger, formState: { isValid, errors } } = useForm({ defaultValues: {...} }); useEffect(() => { trigger(); // 触发初始验证 }, [trigger]);
2. Proxy 订阅机制问题(最常见渲染优化坑)
-
formState被 Proxy 包裹,以优化性能,只订阅你实际读取的状态。 -
如果你这样写:
tsxconst { formState } = useForm(); // 渲染中条件性访问 return <button disabled={!formState.isValid}>提交</button>;由于
isValid是条件访问,Proxy 不会订阅 它的变化,导致它永远停留在初始false。 -
解决 :在渲染前解构读取所有需要的状态(强制订阅):
tsxconst { formState: { isValid, isDirty, errors } } = useForm(); // 或至少读取 isValid const { isValid } = formState; return <button disabled={!isDirty || !isValid}>提交</button>; -
如果在
useEffect中监听,也要放整个formState:tsxuseEffect(() => { console.log(isValid); }, [formState]); // 正确
3. mode 配置影响验证时机
-
默认
mode: 'onSubmit':只有提交时才完整验证,isValid在提交前通常为false。 -
如果用
mode: 'onChange'或'all',输入时实时验证,isValid会及时更新。 -
但即使验证通过,如果字段未被"touch"(如异步填充值或未交互),
isValid可能滞后。 -
建议 :实时表单用
mode: 'onChange'或'all':tsxuseForm({ mode: 'onChange' });
4. 异步值填充或动态字段
-
用
setValue异步设置值(如 API 数据)时,如果不带{ shouldValidate: true },不会触发验证,isValid不更新。tsxsetValue('field', value, { shouldValidate: true }); -
动态添加字段(如
useFieldArray或条件渲染)时,isValid可能短暂为false,需等待重新验证。
5. 其他边缘情况
- 使用
setError手动设置错误会强制isValid为false。 - Resolver(如 Yup/Zod)时,初始无效
defaultValues会让isValid为false,但errors可能为空(需trigger()显式显示错误)。 - 测试环境(如 Cypress/Jest)中,由于异步渲染,
isValid可能滞后,需要await waitFor。
调试建议
- 打印整个状态:
console.log({ isValid, errors, isValidating, dirtyFields })。 - 检查是否订阅了
isValid(Proxy 问题)。 - 如果表单初始有效但按钮禁用,优先检查订阅和
trigger()。
大多数情况下,结合 mode: 'onChange' + 正确解构 isValid + 必要时 trigger() 就能解决"有时 false"的问题。如果提供你的 useForm 配置和使用代码,能更精准定位!