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
相关产品推荐
相关产品推荐

