0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React 19の `useActionState` と `useOptimistic` でフォーム処理のボイラープレートを一掃する

0
Posted at

⚠️ この記事について

この記事はAI(Claude)を活用して作成しています。
以下の点にご注意ください:

  • 情報の鮮度: 記事作成時点(2026年6月)の情報です。ライブラリのバージョンアップ等により内容が古くなる場合があります
  • 動作確認: コードサンプルは必ず実際の環境でテスト・検証してください
  • 出典の確認: 重要な実装判断は本文中の参考リンクや公式ドキュメントで必ず一次確認をお願いします
  • 誤りの可能性: AI生成コンテンツには誤りが含まれる場合があります。お気づきの点はコメントでご指摘ください

はじめに

React でフォーム送信や非同期処理を実装するとき、こんなコードを書いたことはありませんか?

const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [data, setData] = useState(null);

const handleSubmit = async (e: React.FormEvent) => {
  e.preventDefault();
  setIsLoading(true);
  setError(null);
  try {
    const result = await submitForm(formData);
    setData(result);
  } catch (err) {
    setError('エラーが発生しました');
  } finally {
    setIsLoading(false);
  }
};

ローディング・エラー・データのために useState を3つ並べ、try/catch/finally で状態をひとつひとつ管理する…。これはほぼすべてのフォームで繰り返されるボイラープレートです。

React 19 では useActionStateuseOptimistic という2つの新しいHooksが正式に導入され、このような定型コードを大幅に削減できるようになりました。本記事ではその使い方を実例とともに解説します。


環境 / 前提条件

項目 バージョン
React 19.2.4 以上 ※
Node.js 18.x 以上
TypeScript 5.x(任意)

※ React 19系にはServer Componentsに関する複数のセキュリティ脆弱性が報告されており、バージョン 19.0.4 / 19.1.5 / 19.2.4 以降へのアップグレードが強く推奨されています1

インストール:

npm install react@latest react-dom@latest

従来のフォーム処理の課題

React 18以前の典型的なフォーム実装では、1つのフォーム送信に対して最低でも以下の状態管理が必要でした2

  • isLoading(通信中かどうか)
  • error(エラーメッセージ)
  • data(レスポンスデータ)
  • 楽観的UIが必要な場合はさらに「送信前のスナップショット」と「手動ロールバック処理」

これが30〜50行のボイラープレートとなり、コンポーネントを肥大化させる原因になっていました。


useActionState で状態管理をまとめる

基本構文

useActionState は非同期アクション(Action)の「ペンディング状態・結果・エラー」を一括管理するHooksです3

import { useActionState } from 'react';

const [state, dispatchAction, isPending] = useActionState(actionFn, initialState);
返り値 説明
state アクション実行後の最新状態(初回は initialState
dispatchAction アクションを呼び出す関数
isPending アクション実行中は true

実例:ログインフォーム

import { useActionState } from 'react';

type FormState = {
  success: boolean | null;
  error: string | null;
};

async function loginAction(
  prevState: FormState,
  formData: FormData
): Promise<FormState> {
  const email = formData.get('email') as string;
  const password = formData.get('password') as string;

  // バリデーション
  if (!email || !password) {
    return { success: false, error: 'メールアドレスとパスワードを入力してください' };
  }

  try {
    await apiLogin({ email, password }); // 実際のAPI呼び出し
    return { success: true, error: null };
  } catch {
    return { success: false, error: 'ログインに失敗しました' };
  }
}

export function LoginForm() {
  const [state, submitAction, isPending] = useActionState(loginAction, {
    success: null,
    error: null,
  });

  return (
    <form action={submitAction}>
      <input name="email" type="email" placeholder="メールアドレス" />
      <input name="password" type="password" placeholder="パスワード" />
      <button type="submit" disabled={isPending}>
        {isPending ? 'ログイン中...' : 'ログイン'}
      </button>
      {state.error && <p style={{ color: 'red' }}>{state.error}</p>}
      {state.success && <p style={{ color: 'green' }}>ログインしました!</p>}
    </form>
  );
}

ポイントは以下の3点です:

  1. useState が不要:ローディング・エラー・結果をすべて useActionState が管理
  2. formaction 属性に関数を渡せる:React 19 では <form action={fn}> の形式をサポート3
  3. アクション関数は純粋に書ける:副作用のロジックをコンポーネント外に切り出せる

useOptimistic で即時UIフィードバックを実現する

楽観的UI(Optimistic UI)とは

ネットワーク通信の完了を待たずに、UIを先に更新して「いかにも処理が終わった」ように見せる手法です。サーバーからエラーが返ってきたときは自動的に元の状態へ戻します。

基本構文

import { useOptimistic } from 'react';

const [optimisticState, addOptimistic] = useOptimistic(state, updateFn);
引数/返り値 説明
state 実際のデータ(サーバーから取得した正の状態)
updateFn 楽観的な更新を定義する関数 (currentState, optimisticValue) => newState
optimisticState 楽観的に更新されたUI用の状態
addOptimistic 楽観的な更新を適用する関数

実例:Todoリスト

import { useActionState, useOptimistic } from 'react';

type Todo = { id: string; text: string; sending?: boolean };

async function addTodoAction(
  prevTodos: Todo[],
  formData: FormData
): Promise<Todo[]> {
  const text = formData.get('text') as string;
  const newTodo = await createTodo({ text }); // API呼び出し
  return [...prevTodos, newTodo];
}

export function TodoList({ initialTodos }: { initialTodos: Todo[] }) {
  const [todos, submitAction, isPending] = useActionState(addTodoAction, initialTodos);

  // 楽観的UIの設定
  const [optimisticTodos, addOptimisticTodo] = useOptimistic(
    todos,
    (currentTodos: Todo[], newText: string) => [
      ...currentTodos,
      { id: `temp-${Date.now()}`, text: newText, sending: true },
    ]
  );

  const handleAction = async (formData: FormData) => {
    const text = formData.get('text') as string;
    // ← awaitより前に呼び出すことが重要[^4]
    addOptimisticTodo(text);
    await submitAction(formData);
  };

  return (
    <div>
      <ul>
        {optimisticTodos.map((todo) => (
          <li key={todo.id} style={{ opacity: todo.sending ? 0.5 : 1 }}>
            {todo.text}
            {todo.sending && ' (送信中...)'}
          </li>
        ))}
      </ul>
      <form action={handleAction}>
        <input name="text" placeholder="新しいTodo" />
        <button type="submit" disabled={isPending}>追加</button>
      </form>
    </div>
  );
}

useOptimistic の仕組み

useOptimistic はReactのトランジションシステムに連動しています2。アクションが完了すると:

  • 成功した場合state(サーバーからの正の状態)に自動的に置き換わる
  • 失敗した場合state は変化せず、楽観的な更新は自動的に消える

手動でスナップショットを取ったり、catch でロールバック処理を書く必要がありません。


useActionStateuseOptimistic の使い分け

用途 推奨Hook
フォーム送信のローディング・エラー管理 useActionState
送信中に即座にUIを更新したい useOptimistic
両方が必要(送信 + 楽観的UI) 組み合わせて使う

両者は補完関係にあります。useActionState でアクションのライフサイクルを管理しつつ、useOptimistic で即時フィードバックを提供するのが典型的なパターンです2


React Queryなどのライブラリとの使い分け

useActionStateuseOptimistic はコンポーネント内のローカルなアクション状態管理に特化しています。グローバルなサーバーキャッシュ・バックグラウンドリフェッチ・複数コンポーネント間のキャッシュ同期が必要な場合は、引き続き React Query(TanStack Query)や SWR が有効です2


まとめ

  • useActionState はフォーム送信など非同期アクションの「ペンディング状態・結果・エラー」を1つのHooksで管理し、複数の useStatetry/catch/finally ボイラープレートを不要にする
  • useOptimistic は楽観的UI更新を宣言的に記述でき、失敗時の自動ロールバックが組み込まれている
  • 2つを組み合わせることで、以前30〜50行必要だったフォームロジックを約12行程度に圧縮できる2
  • React 19系を使う場合はセキュリティパッチ済みの 19.0.4 / 19.1.5 / 19.2.4 以上を使用すること1

参考情報

  1. Denial of Service and Source Code Exposure in React Server Components - react.dev - 参照日: 2026-06-28 2

  2. React 19's useOptimistic and useActionState: Replacing 80% of Your State Boilerplate - SitePoint - 参照日: 2026-06-28 2 3 4 5

  3. React v19 – React公式ブログ - react.dev - 参照日: 2026-06-28 2

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?