Safe Action Forms

next-safe-action/skills/skills/safe-action-forms

作者 next-safe-actiona2605bd2e84321245cba8f5718c144a6e4a5fa47无许可证收录于 2026年10月9日更新于 2026年10月9日

Use when integrating next-safe-action with forms -- react-hook-form adapter (useHookFormAction, useHookFormOptimisticAction, mapToHookFormErrors), native HTML forms, bind arguments, or file uploads

AI 生成的概览

指导如何将 next-safe-action 与 React 表单集成,包括原生表单、react-hook-form 适配器和文件上传。

功能
说明如何在 React/Next.js 应用中把 next-safe-action 服务端操作接入表单。它对比了 useAction 搭配原生表单、useStateAction 搭配 form action、useHookFormAction 与 useHookFormOptimisticAction 等方案,并给出各自的代码示例。内容还涉及校验错误展示、服务端错误、等待状态、bind 参数和文件上传,并列出适配器包的入口与导出。
适用场景
适用于在使用 next-safe-action 的 Next.js 项目中构建或审查表单,并需要选择表单集成方案的场景。适合涉及字段级校验错误、乐观更新、原生表单提交或文件上传的情况。不适用于与 next-safe-action 无关的一般 React 表单设计。
运行要求
无需脚本,仅提供说明。假定项目为使用 next-safe-action 的 React/Next.js 项目;若使用 React Hook Form 方案,还需要 @next-safe-action/adapter-react-hook-form 包、react-hook-form、@hookform/resolvers/zod 等解析器以及 zod。

next-safe-action Form Integration

Options

ApproachWhen to Use
useAction + native formSimple forms, no complex validation UI, programmatic triggers
useStateAction + <form action={formAction}>Forms with state tracking, need prevResult access, full callbacks
useHookFormAction (RHF adapter)Complex forms with field-level errors, validation on change/blur
useHookFormOptimisticActionRHF forms with optimistic UI updates
useOptimisticStateActionInstant UI where overlapping writes must be queued and accumulate (see the hooks skill)

Quick Start — useStateAction Form

tsx
"use client";
import { useStateAction } from "next-safe-action/hooks";import { submitContact } from "@/app/actions";
export function ContactForm() {  const { formAction, result, isPending, hasSucceeded } = useStateAction(submitContact, {    onSuccess: () => toast.success("Message sent!"),  });
  return (    <form action={formAction}>      <input name="name" required />      <input name="email" type="email" required />      <textarea name="message" required />
      {result.validationErrors?.email && (        <p>{result.validationErrors.email._errors?.[0]}</p>      )}      {result.serverError && <p>{result.serverError}</p>}      {hasSucceeded && <p>Message sent!</p>}
      <button type="submit" disabled={isPending}>        {isPending ? "Sending..." : "Send"}      </button>    </form>  );}

Note: useStateAction requires the server action to be defined with .stateAction() instead of .action(), and <form action={formAction}> passes raw FormData, so its input schema must parse FormData (e.g. zfd.formData({...}) from zod-form-data). See the hooks skill for the full decision table on when to use useAction vs useStateAction.

Quick Start — Native Form

tsx
"use client";
import { useAction } from "next-safe-action/hooks";import { submitContact } from "@/app/actions";
export function ContactForm() {  const { execute, result, isPending } = useAction(submitContact);
  return (    <form      onSubmit={(e) => {        e.preventDefault();        const fd = new FormData(e.currentTarget);        execute({          name: fd.get("name") as string,          email: fd.get("email") as string,          message: fd.get("message") as string,        });      }}    >      <input name="name" required />      <input name="email" type="email" required />      <textarea name="message" required />
      {result.validationErrors && (        <p>{result.validationErrors.email?._errors?.[0]}</p>      )}      {result.serverError && <p>{result.serverError}</p>}      {result.data && <p>Message sent!</p>}
      <button type="submit" disabled={isPending}>        {isPending ? "Sending..." : "Send"}      </button>    </form>  );}

Quick Start — React Hook Form Adapter

tsx
"use client";
import { useHookFormAction } from "@next-safe-action/adapter-react-hook-form/hooks";import { zodResolver } from "@hookform/resolvers/zod";import { z } from "zod";import { submitContact } from "@/app/actions";
const schema = z.object({  name: z.string().min(1, "Name is required"),  email: z.string().email("Invalid email"),  message: z.string().min(10, "Message must be at least 10 characters"),});
export function ContactForm() {  const { form, handleSubmitWithAction, action } = useHookFormAction(    submitContact,    zodResolver(schema),    {      actionProps: {        onSuccess: () => toast.success("Message sent!"),      },    }  );
  return (    <form onSubmit={handleSubmitWithAction}>      <input {...form.register("name")} />      {form.formState.errors.name && <p>{form.formState.errors.name.message}</p>}
      <input {...form.register("email")} />      {form.formState.errors.email && <p>{form.formState.errors.email.message}</p>}
      <textarea {...form.register("message")} />      {form.formState.errors.message && <p>{form.formState.errors.message.message}</p>}
      {action.result.serverError && <p>{action.result.serverError}</p>}
      <button type="submit" disabled={action.isPending}>        {action.isPending ? "Sending..." : "Send"}      </button>    </form>  );}

Supporting Docs

  • Native form submission patterns
  • React Hook Form adapter in depth
  • File uploads

Entry Points

PackageEntry PointExports
@next-safe-action/adapter-react-hook-formDefaultmapToHookFormErrors, types
@next-safe-action/adapter-react-hook-form/hooksHooksuseHookFormAction, useHookFormOptimisticAction, useHookFormActionErrorMapper

来源与署名

来源:next-safe-action/skills位于skills/safe-action-forms提交a2605bd

许可证: 无许可证

内容归原作者所有。SourceWeft 从公开仓库中收录这些内容。

举报或申请下架