Next.js14中react-hook-form的onSubmit模式提交不触发校验问题
在使用 Next.js14 结合 Server Actions 开发表单时,确实会遇到 react-hook-form 默认 onSubmit 模式下客户端校验不触发、isValid 为 false 但 errors 为空的问题,下面是实际项目中验证有效的解决方法:
核心问题原因
当直接把 Server Action 传给 handleSubmit 时,onSubmit 模式下 react-hook-form 的客户端校验流程会被 Server Action 的异步提交逻辑跳过——因为 Server Action 是在服务端执行的,客户端没有捕获到校验错误就直接发起了请求,导致错误无法展示。而 isValid 为 false 但 errors 为空,大概率是校验触发了但没有正确关联到表单字段,或者 Zod Schema 配置有遗漏。
解决步骤
1. 手动触发客户端校验后再调用 Server Action
不要直接将 Server Action 作为 handleSubmit 的参数,而是先通过 handleSubmit 包裹自定义逻辑,手动触发全量校验,通过后再执行 Server Action:
'use client'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { zodResolver } from '@hookform/resolvers/zod'; import { submitForm } from '@/app/actions'; // 定义 Zod Schema const FormSchema = z.object({ email: z.string().email('请输入有效的邮箱地址'), password: z.string().min(6, '密码至少需要6位'), }); type FormValues = z.infer<typeof FormSchema>; export default function LoginForm() { const { register, handleSubmit, formState: { errors, isValid } } = useForm<FormValues>({ resolver: zodResolver(FormSchema), mode: 'onSubmit', // 保持默认模式 }); const handleFormSubmit = async (data: FormValues) => { // 手动触发校验,通过后执行 Server Action await handleSubmit(async (validatedData) => { await submitForm(validatedData); })(); }; return ( <form onSubmit={handleSubmit(handleFormSubmit)}> <div> <label htmlFor="email">邮箱</label> <input {...register('email')} id="email" type="email" /> {/* 展示客户端校验错误 */} {errors.email && <p className="text-red-500">{errors.email.message}</p>} </div> <div> <label htmlFor="password">密码</label> <input {...register('password')} id="password" type="password" /> {errors.password && <p className="text-red-500">{errors.password.message}</p>} </div> <button type="submit" disabled={!isValid}>提交</button> </form> ); }
2. 检查 Zod Schema 配置
确保 Schema 中的每个必填字段没有误加 optional() 或 nullable(),否则空值不会触发校验错误。如果字段是必填项,保持基础规则即可:
// 错误示例:加了 optional() 会导致空值不触发校验 const FormSchema = z.object({ email: z.string().email().optional(), }); // 正确示例:必填字段不添加 optional() const FormSchema = z.object({ email: z.string().email('请输入有效的邮箱地址'), });
3. 校验表单字段与 register 的关联
确认每个输入框都正确调用了 register('字段名'),字段名要和 Zod Schema 中的名称完全一致,拼写错误会导致校验错误无法关联到对应字段。
排查 isValid 为 false 但 errors 为空的问题
可以在组件中打印表单状态,确认哪些字段触发了校验:
const { formState: { errors, isValid, dirtyFields, touchedFields } } = useForm(...); // 控制台查看字段状态 console.log('已修改字段:', dirtyFields); console.log('已触碰字段:', touchedFields); console.log('校验错误:', errors);
如果有未注册的字段被包含在提交数据中,也会导致 isValid 为 false 但 errors 为空,此时需要清理多余的字段,或者在 Schema 中明确允许或忽略这些字段。
服务端校验保持一致
Server Action 中也要复用相同的 Zod Schema 做校验,确保双端规则统一:
// app/actions.ts 'use server'; import { z } from 'zod'; // 复用客户端的 Schema,或者单独定义相同规则的 Schema const FormSchema = z.object({ email: z.string().email(), password: z.string().min(6), }); export async function submitForm(data: unknown) { // 服务端校验,不通过会抛出 ZodError const validatedData = FormSchema.parse(data); // 处理表单提交逻辑,比如存入数据库、调用接口等 console.log('校验通过的数据:', validatedData); }
内容的提问来源于stack exchange,提问作者Jerome

