safe-action-forms について
ネイティブフォームからReact Hook Formアダプターまで、next-safe-actionのフォーム連携パターンを解説するスキルです。フォーム状態、検証、楽観的更新に応じたHookの選択を支援します。
主な機能
- 01useActionでネイティブフォームを使用
- 02useStateActionでフォーム状態を追跡
- 03React Hook Formアダプターを接続
- 04フォームの楽観的更新に対応
- 05FormDataとスキーマ要件を処理
活用シーン
- 01シンプルなActionフォームを構築
- 02フィールド単位の検証エラーを表示
- 03楽観的更新を使うフォームを作成
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ next-safe-action/ skills --skill safe-action-formsインストール後、Claude Code で/safe-action-formsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
file-uploads.mdform-actions.mdreact-hook-form.md詳細
next-safe-action フォーム連携
オプション
| アプローチ | 使用する場面 |
|---|---|
useAction + ネイティブフォーム | シンプルなフォーム、複雑なバリデーション UI が不要な場合、プログラムによるトリガー |
useStateAction + <form action={formAction}> | 状態の追跡が必要なフォーム、prevResult へのアクセスが必要な場合、コールバックをすべて使用する場合 |
useHookFormAction(RHF アダプター) | フィールド単位のエラーや、変更時/フォーカス解除時のバリデーションが必要な複雑なフォーム |
useHookFormOptimisticAction | 楽観的 UI 更新を行う RHF フォーム |
useOptimisticStateAction | 重複する書き込みをキューに入れて蓄積する必要がある、即時反映の UI(hooks スキルを参照) |
クイックスタート — useStateAction フォーム
"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>
);
}
注:useStateAction を使うには、サーバーアクションを .action() ではなく .stateAction() で定義する必要があります。また、<form action={formAction}> は生の FormData を渡すため、入力スキーマは FormData を解析する必要があります(例:zod-form-data の zfd.formData({...}))。useAction と useStateAction のどちらを使うべきかについて、詳しい判断表はhooks スキルを参照してください。
クイックスタート — ネイティブフォーム
"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>
);
}
クイックスタート — React Hook Form アダプター
"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>
);
}
関連ドキュメント
エントリーポイント
| パッケージ | エントリーポイント | エクスポート |
|---|---|---|
@next-safe-action/adapter-react-hook-form | デフォルト | mapToHookFormErrors、型 |
@next-safe-action/adapter-react-hook-form/hooks | フック | useHookFormAction、useHookFormOptimisticAction、useHookFormActionErrorMapper |
コメント
Markdown 対応