登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

Next.js Server Action 返回校验错误时怎么保留表单状态

来源:17golang原创

时间:2026-09-09 05:15:30 308浏览 收藏

Next.js 的 Server Action 校验失败时,表单不要只返回一条字符串错误。更稳妥的做法是让 Action 返回一个可序列化的状态对象,同时带上 valueserrors,客户端再用 useActionState 接住它。这样用户输入的姓名和邮箱会在服务端拒绝后继续显示,只需要修改有问题的字段。

核心写法是:服务端从 FormData 读取值,校验失败时原样回传安全的字段值和字段错误;客户端把返回的值绑定回输入框。预期的业务校验不要用 throw,真正的系统异常再交给错误边界。
要点速览
  • useActionState 的 action 首参是上一轮状态,第二个参数才是提交的 FormData
  • 失败状态至少保留可回显的 values 与字段级 errors,不要把密码等敏感字段回传。
  • 校验失败返回对象,数据库或网络故障抛出异常并交给 Error Boundary。

一、先把表单状态设计成可回显的数据

Server Action 的返回值会成为下一次渲染的状态,因此结构要简单、稳定、可序列化。下面只回显姓名和邮箱;如果表单有密码、一次性验证码或文件,不要把它们放进返回值。

字段用途失败时处理
values保留用户刚提交的安全字段绑定回 input 的 value
errors字段级校验信息显示在对应字段下方
message / ok表单级结果显示成功或通用提示

这个结构把“用户输入”和“服务端意见”分开,后续增加手机号或公司名时,只扩展对应字段,不必让组件猜测一条错误字符串应该放在哪里。

Next.js Server Action 表单状态框图:FormData、校验器、values、errors 与客户端输入框的关系
图1:静态查看表单提交数据如何映射到校验器,再分别进入可回显的 values 和字段 errors。

二、校验失败时从 Server Action 回传 values

Server Action 文件放在服务端边界内,先读取字符串,再做长度和格式判断。示例中的 saveProfile 没有把前一次状态当成表单值来源,而是以本次提交的 FormData 为准;previousState 主要用于满足 Action 签名和扩展连续提交状态。

// app/actions/profile.ts
'use server'

type ActionState = {
  ok: boolean
  values: { name: string; email: string }
  errors: { name?: string; email?: string }
  message?: string
}

export const initialProfileState: ActionState = {
  ok: false,
  values: { name: '', email: '' },
  errors: {},
}

export async function saveProfile(
  previousState: ActionState,
  formData: FormData,
): Promise {
  // 只读取允许回显的字段,避免把敏感字段带回客户端
  const name = String(formData.get('name') ?? '').trim()
  const email = String(formData.get('email') ?? '').trim()
  const errors: ActionState['errors'] = {}

  // 预期的业务校验用返回值表达,不用 throw
  if (name.length  0) {
    return { ok: false, values: { name, email }, errors }
  }

  // 生产环境还要在这里检查身份、权限,并执行持久化
  await persistProfile({ name, email })
  return { ok: true, values: { name, email }, errors: {}, message: '资料已保存' }
}

async function persistProfile(profile: { name: string; email: string }) {
  // 示例占位:实际项目在此调用数据库或领域服务
  void profile
}

注意第一个参数不能写成 formData。使用 useActionState 后,React 会把上一轮状态放在第一位,把本次提交的 FormData 放在第二位。成功路径可以返回 ok: true,也可以在持久化并刷新数据后重定向;不要在 redirect 后继续依赖返回值。

三、用 useActionState 把返回状态绑定回输入框

客户端组件把返回的 dispatcher 直接交给 form action。关键点是输入框使用 state.values 作为受控值,错误文字使用同一个状态对象;服务端返回失败对象后,组件重新渲染,用户刚输入的内容就不会消失。

// app/profile/profile-form.tsx
'use client'

import { useActionState, useEffect, useState } from 'react'
import { initialProfileState, saveProfile } from '@/app/actions/profile'

export function ProfileForm() {
  const [state, formAction, pending] = useActionState(
    saveProfile,
    initialProfileState,
  )
  const [fields, setFields] = useState(state.values)

  useEffect(() => {
    // 只有服务端返回新 values 时回填,用户输入过程不会被覆盖
    setFields(state.values)
  }, [state.values.name, state.values.email])

  return (
    
setFields({ ...fields, name: event.target.value })} aria-invalid={Boolean(state.errors.name)} aria-describedby="name-error" /> setFields({ ...fields, email: event.target.value })} aria-invalid={Boolean(state.errors.email)} aria-describedby="email-error" /> {state.message &&

{state.message}

}
) }

示例把输入值放在本地 fields 中,用户编辑时即时更新;当服务端返回新的 state.values 时,再通过 useEffect 回填。无论选择受控还是非受控方案,name 属性都不能省略,否则字段不会进入 FormData

React useActionState 表单状态框图:dispatcher 连接 form,返回状态连接输入值、字段错误和提交按钮
图2:静态查看 useActionState 返回的 dispatcher 与 form,以及 state 对输入值、错误提示和 pending 状态的分工。

四、把校验错误和意外异常分开处理

格式不对、邮箱已存在等可预期结果,应作为普通返回值留在当前表单中;数据库不可用、代码空指针或未处理的网络故障,才应该抛出异常并由路由级 Error Boundary 展示兜底页面。这样用户能修正输入,也不会把内部堆栈暴露到页面。

发布前逐项检查:Server Action 内再次做身份与权限校验;返回对象只含可序列化且允许公开的字段;每个输入保留 name;错误提示有 role="alert" 或等效可访问性语义;提交按钮根据 pending 禁用,避免重复提交。若成功后需要显示新数据,再按页面缓存策略调用 revalidatePath 或其他合适的刷新方法。

相关问题

为什么只返回 errors,输入框还是空了?

因为重新渲染时没有可用的字段值。服务端应从本次 FormData 生成安全的 values,并由客户端绑定回输入框。

校验失败应该 throw 吗?

通常不应该。校验失败是用户可修正的业务结果,返回 ActionState 更适合;真正的系统异常再交给 Error Boundary。

为什么 action 的参数顺序容易写错?

useActionState 会把 reducer action 的首参改为 previousState,FormData 变成第二参。若不使用该 Hook,直接传给 form 的 Server Action 才是单参数 FormData。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>