概要
Server Actions を使うとフォーム送信を API Route なしでサーバー側で処理できる。
useActionState(旧 useFormState)と組み合わせることで、サーバーエラーをクライアントに返す型安全なパターンを実現する。
Action 定義
// src/app/contact/_actions/submit.ts
"use server";
import { z } from "zod";
const schema = z.object({
name: z.string().min(1, "名前は必須です"),
message: z.string().min(10, "メッセージは 10 文字以上で入力してください"),
});
export type ActionState = {
errors?: Record<string, string[]>;
success?: boolean;
};
export async function submitContact(
_prev: ActionState,
formData: FormData
): Promise<ActionState> {
const result = schema.safeParse({
name: formData.get("name"),
message: formData.get("message"),
});
if (!result.success) {
return { errors: result.error.flatten().fieldErrors };
}
// TODO: DB 保存や送信処理
console.log("Received:", result.data);
return { success: true };
}
フォームコンポーネント
// src/app/contact/page.tsx
"use client";
import { useActionState } from "react";
import { submitContact, type ActionState } from "./_actions/submit";
const initialState: ActionState = {};
export default function ContactPage() {
const [state, action, isPending] = useActionState(submitContact, initialState);
if (state.success) {
return <p className="text-green-600">送信が完了しました。</p>;
}
return (
<form action={action} className="space-y-4 max-w-sm">
<div>
<label htmlFor="name" className="block text-sm font-medium">
名前
</label>
<input
id="name"
name="name"
className="mt-1 block w-full rounded border px-3 py-2 text-sm"
/>
{state.errors?.name && (
<p className="mt-1 text-xs text-red-600">{state.errors.name[0]}</p>
)}
</div>
<div>
<label htmlFor="message" className="block text-sm font-medium">
メッセージ
</label>
<textarea
id="message"
name="message"
rows={4}
className="mt-1 block w-full rounded border px-3 py-2 text-sm"
/>
{state.errors?.message && (
<p className="mt-1 text-xs text-red-600">{state.errors.message[0]}</p>
)}
</div>
<button
type="submit"
disabled={isPending}
className="w-full rounded bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700 disabled:opacity-50"
>
{isPending ? "送信中..." : "送信"}
</button>
</form>
);
}
ポイント
"use server"ディレクティブで Server Action を定義するuseActionStateでアクションの状態(エラー / 成功)を管理する- Zod の
flatten().fieldErrorsでフィールド単位のエラーを取得する