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

Next.js14中react-hook-form的onSubmit模式提交不触发校验问题

Next.js14 + Server Actions 下 react-hook-form Zod 双端校验的 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 14:43:24