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

NextJS 14 Server Actions+react-hook-form<Controller>提交空Payload问题

解决NextJS 14+ App Router中Server Action接收Controller嵌套表单空Payload问题

以下是针对你的问题的具体解决方案,按优先级排序:

1. 修正Controller的嵌套字段命名与提交方式

核心问题是Controller组件的嵌套字段未正确同步到表单提交数据中,需确保字段命名与表单状态结构一致,并直接传递表单值给Server Action:

表单组件示例(Client Component)

"use client";

import { useForm, Controller, register } from "react-hook-form";
import { z } from "zod";
import { submitForm } from "@/app/actions";

// 定义Zod验证规则
const FormSchema = z.object({
  name: z.string().min(1, "姓名不能为空"),
  user: z.object({
    email: z.string().email("请输入合法邮箱"),
  }),
});

type FormValues = z.infer<typeof FormSchema>;

export default function RegistrationForm() {
  const { control, handleSubmit, formState: { errors } } = useForm<FormValues>({
    defaultValues: {
      name: "",
      user: {
        email: "",
      },
    },
  });

  // 直接传递表单值给Server Action
  const onSubmit = handleSubmit(async (formValues) => {
    await submitForm(formValues);
  });

  return (
    <form onSubmit={onSubmit} className="space-y-4">
      {/* 普通register字段 */}
      <div>
        <input
          {...register("name")}
          placeholder="姓名"
          className="border p-2 w-full"
        />
        {errors.name && <p className="text-red-500 text-sm">{errors.name.message}</p>}
      </div>

      {/* Controller嵌套字段 */}
      <div>
        <Controller
          name="user.email" // 必须对应嵌套路径
          control={control}
          render={({ field }) => (
            <input
              {...field}
              type="email"
              placeholder="邮箱"
              className="border p-2 w-full"
            />
          )}
        />
        {errors.user?.email && <p className="text-red-500 text-sm">{errors.user.email.message}</p>}
      </div>

      <button type="submit" className="bg-blue-500 text-white p-2 w-full">
        提交
      </button>
    </form>
  );
}

Server Action示例(app/actions.ts)

直接接收表单对象参数,无需手动解析FormData:

"use server";

import { z } from "zod";
import { GoogleSpreadsheet } from "google-spreadsheet";

const FormSchema = z.object({
  name: z.string().min(1),
  user: z.object({
    email: z.string().email(),
  }),
});

export async function submitForm(formData: z.infer<typeof FormSchema>) {
  // 先做Zod验证
  const validatedData = FormSchema.parse(formData);

  // 对接Google Spreadsheet API逻辑
  const doc = new GoogleSpreadsheet(process.env.GOOGLE_SHEET_ID);
  await doc.useServiceAccountAuth({
    client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL!,
    private_key: process.env.GOOGLE_PRIVATE_KEY!.replace(/\\n/g, "\n"),
  });
  await doc.loadInfo();
  const sheet = doc.sheetsByIndex[0];
  await sheet.addRow({
    name: validatedData.name,
    email: validatedData.user.email,
  });
}

2. 若必须使用FormData提交的兼容方案

如果你的业务场景需要手动构建FormData,需将嵌套字段以父字段.子字段的格式append:

修改表单提交逻辑

const onSubmit = handleSubmit(async (formValues) => {
  const formData = new FormData();
  formData.append("name", formValues.name);
  formData.append("user.email", formValues.user.email); // 嵌套字段用点分隔
  await submitForm(formData);
});

修改Server Action解析逻辑

export async function submitForm(formData: FormData) {
  // 手动解析嵌套字段
  const rawData = {
    name: formData.get("name"),
    user: {
      email: formData.get("user.email"),
    },
  };
  const validatedData = FormSchema.parse(rawData);

  // 后续Google Spreadsheet操作同上
}

3. 排查常见错误点

  • 确认Controller的name属性与useForm的defaultValues结构完全匹配,比如嵌套字段必须写user.email而非email
  • 检查Controller的render函数是否正确传递了field对象的所有属性(value、onChange、onBlur),避免自定义逻辑导致值未同步到表单状态
  • 确保Server Action没有误用formData.get("email"),嵌套字段的键应为user.email

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 20:15:55