useActionState 完全入門【React】フォームアクションの状態管理
ReactのuseActionStateとは何か、フォーム送信後の状態(成功・エラー・ペンディング)を管理する方法を解説。React 19以降で追加されたサーバーアクションとの組み合わせ方も紹介します。
フォーム送信後の「送信中」「成功」「エラー」という状態管理は、すべてのWebアプリで共通の課題です。useActionState はこのフォームのアクション結果に基づいた状態管理を、シンプルに実装するためのフックです。
React 19 から追加されたこのフックを使うと、JavaScriptが読み込まれる前でもフォームが機能する(Progressive Enhancement)実装が実現できます。
この記事でわかること:
useActionStateが解決する問題- 3つのパラメータと戻り値の意味
- フォーム送信の実装例
- サーバーアクションとの組み合わせ(React 19+)
useActionState とは?
useActionState は、フォームのアクション結果に基づいて state を更新するフックです。
const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);| パラメータ | 説明 |
|---|---|
fn | フォーム送信時に呼ばれる関数。第1引数は前の state、第2引数以降は通常のフォームデータ |
initialState | 初期状態として使う任意のシリアライズ可能な値 |
permalink(省略可能) | Progressive Enhancement 用のユニークなURL |
| 戻り値 | 説明 |
|---|---|
state | 現在の state(初回は initialState) |
formAction | <form action> に渡す新しいアクション |
isPending | アクションが処理中かどうかを示す boolean |
基本的な使い方
import { useActionState } from "react";
// フォームのアクション関数
async function submitAction(prevState, formData) {
const name = formData.get("name");
const email = formData.get("email");
// バリデーション
if (!name || !email) {
return { error: "全ての項目を入力してください", success: false };
}
try {
await saveToDatabase({ name, email });
return { message: "送信が完了しました!", success: true };
} catch (e) {
return { error: "送信に失敗しました。もう一度お試しください。", success: false };
}
}
function ContactForm() {
const [state, formAction, isPending] = useActionState(submitAction, {
message: "",
error: "",
success: false,
});
return (
<form action={formAction}>
{state.success && (
<p style={{ color: "green" }}>{state.message}</p>
)}
{state.error && (
<p style={{ color: "red" }}>{state.error}</p>
)}
<div>
<label htmlFor="name">お名前</label>
<input id="name" name="name" type="text" required />
</div>
<div>
<label htmlFor="email">メールアドレス</label>
<input id="email" name="email" type="email" required />
</div>
<button type="submit" disabled={isPending}>
{isPending ? "送信中..." : "送信する"}
</button>
</form>
);
}アクション関数の仕組み
useActionState に渡すアクション関数は、通常の <form action> に渡す関数と少し異なります。
// 通常のフォームアクション
async function normalAction(formData) {
const name = formData.get("name");
}
// useActionState 用のアクション(第1引数に前の state が追加される)
async function actionWithState(prevState, formData) {
const name = formData.get("name");
// prevState で前回の結果を参照できる
console.log("前回の状態:", prevState);
}この「前の state を受け取る」という設計により、送信回数をカウントしたり、前回のエラーをリセットしたりできます。
送信回数をカウントする例
async function incrementAction(prevState) {
return prevState + 1; // 前の値に1を足すだけ
}
function Counter() {
const [count, formAction] = useActionState(incrementAction, 0);
return (
<form action={formAction}>
<p>送信回数: {count}</p>
<button type="submit">+1</button>
</form>
);
}useActionState vs useState + useEffect
従来の方法と比較してみます。
// ❌ 従来:useState + useEffect の組み合わせ(煩雑)
function OldForm() {
const [isSubmitting, setIsSubmitting] = useState(false);
const [result, setResult] = useState(null);
const [error, setError] = useState(null);
const handleSubmit = async (e) => {
e.preventDefault();
setIsSubmitting(true);
try {
const res = await submit(new FormData(e.target));
setResult(res);
} catch (err) {
setError(err.message);
} finally {
setIsSubmitting(false);
}
};
return <form onSubmit={handleSubmit}>...</form>;
}
// ✅ useActionState:シンプルに同じことができる
function NewForm() {
const [state, formAction, isPending] = useActionState(submitAction, null);
return <form action={formAction}>...</form>;
}サーバーアクションとの組み合わせ(Next.js / React 19+)
Next.js の Server Actions と組み合わせると、サーバーサイドの処理もシームレスに統合できます。
// app/actions.ts(サーバーアクション)
"use server";
export async function saveContact(prevState, formData) {
const name = formData.get("name");
// サーバー側で DB に保存
await db.contacts.create({ name });
return { message: "保存しました!" };
}// app/ContactForm.tsx(クライアントコンポーネント)
"use client";
import { useActionState } from "react";
import { saveContact } from "./actions";
export function ContactForm() {
const [state, formAction, isPending] = useActionState(saveContact, null);
return (
<form action={formAction}>
<input name="name" />
<button disabled={isPending}>保存</button>
{state?.message && <p>{state.message}</p>}
</form>
);
}まとめ
useActionStateはフォームアクションの結果を state として管理するフック[state, formAction, isPending]の3つを返す- アクション関数の第1引数に「前の state」が渡される
isPendingでローディング状態の表示が簡単にできる- React 19 の Server Actions と組み合わせることで、Progressive Enhancement が実現できる