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

Next.js中react-hook-form的客户端服务端处理及错误注入问题

问题解决:Next.js中react-hook-form与服务端Action的冲突及服务端ZodError注入方案

一、为什么加onSubmit={form.handleSubmit(...)}后服务端Action不触发?

react-hook-form的handleSubmit默认会调用event.preventDefault(),拦截原生表单的提交行为,导致你设置的action={RegisterAccount}无法触发。解决思路有两种:手动触发原生提交,或者结合Next.js的useActionState绑定服务端状态。


二、方案1:手动触发原生提交(快速兼容)

在handleSubmit的回调里,手动调用原生表单的提交方法,既保留客户端验证,又触发服务端Action:

'use client';

import { useForm, zodResolver } from 'react-hook-form';
import { z } from 'zod';
import { RegisterAccount } from '@/app/actions/register';
import { registerFormSchema } from '@/schemas';

type FormValues = z.infer<typeof registerFormSchema>;

export default function RegisterForm() {
  const form = useForm<FormValues>({
    resolver: zodResolver(registerFormSchema),
    defaultValues: {
      email: '',
      password: ''
    }
  });

  const onSubmit = (data, e) => {
    // 这里可以加客户端提交前的逻辑,比如设置loading状态
    e.target.submit(); // 手动触发原生表单提交,调用服务端Action
  };

  return (
    <form action={RegisterAccount} onSubmit={form.handleSubmit(onSubmit)}>
      <div>
        <label>邮箱</label>
        <input {...form.register('email')} />
        {form.formState.errors.email && <p>{form.formState.errors.email.message}</p>}
      </div>
      <div>
        <label>密码</label>
        <input type="password" {...form.register('password')} />
        {form.formState.errors.password && <p>{form.formState.errors.password.message}</p>}
      </div>
      <button type="submit">注册</button>
    </form>
  );
}

三、方案2:结合useActionState注入服务端ZodError(推荐)

这个方案能直接把服务端返回的ZodError同步到react-hook-form的错误状态里,完美整合客户端与服务端验证:

1. 改造服务端Action函数

让服务端函数返回包含错误信息的状态对象:

// app/actions/register.ts
import { z } from 'zod';
import { registerFormSchema } from '@/schemas';

// 定义返回状态类型
type ActionState = {
  error?: z.ZodError;
};

export async function RegisterAccount(prevState: ActionState, formData: FormData): Promise<ActionState> {
  try {
    // 把FormData转成对象后用zod解析
    const formValues = Object.fromEntries(formData);
    registerFormSchema.parse(formValues);
    
    // 执行注册逻辑(比如存数据库、发验证邮件等)
    return { error: undefined };
  } catch (err) {
    // 捕获ZodError并返回
    if (err instanceof z.ZodError) {
      return { error: err };
    }
    // 处理其他错误
    return { error: new z.ZodError([{ message: '未知错误', path: [] }]) };
  }
}

2. 客户端组件整合useActionState与react-hook-form

'use client';

import { useForm, zodResolver } from 'react-hook-form';
import { z } from 'zod';
import { useActionState, useEffect } from 'react';
import { RegisterAccount } from '@/app/actions/register';
import { registerFormSchema } from '@/schemas';

type FormValues = z.infer<typeof registerFormSchema>;
type ActionState = Awaited<ReturnType<typeof RegisterAccount>>;

export default function RegisterForm() {
  // 用useActionState绑定服务端Action和状态
  const [actionState, formAction] = useActionState(RegisterAccount, { error: undefined });

  const form = useForm<FormValues>({
    resolver: zodResolver(registerFormSchema),
    defaultValues: {
      email: '',
      password: ''
    }
  });

  // 监听服务端返回的错误,同步到react-hook-form
  useEffect(() => {
    if (actionState.error) {
      actionState.error.issues.forEach(issue => {
        // 把ZodError的path映射到表单字段,注入错误信息
        form.setError(issue.path.join('.') as keyof FormValues, {
          message: issue.message
        });
      });
    }
  }, [actionState.error, form]);

  return (
    <form action={formAction} onSubmit={form.handleSubmit(() => {})}>
      <div>
        <label>邮箱</label>
        <input {...form.register('email')} />
        {form.formState.errors.email && <p>{form.formState.errors.email.message}</p>}
      </div>
      <div>
        <label>密码</label>
        <input type="password" {...form.register('password')} />
        {form.formState.errors.password && <p>{form.formState.errors.password.message}</p>}
      </div>
      <button type="submit">注册</button>
    </form>
  );
}

关键说明

  • 客户端验证(react-hook-form+zod)负责即时反馈,提升用户体验;服务端验证是最后一道防线,防止恶意提交。
  • useActionState会自动管理服务端Action的返回状态,每次提交后actionState会更新为服务端返回的结果。
  • 通过form.setError可以精准把服务端返回的字段错误映射到表单对应的输入框上。

内容的提问来源于stack exchange,提问作者ampersanded

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 10:15:35