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 從公開儲存庫中收錄這些內容。

檢舉或申請下架