React Routerのaction完全攻略!実践で役立つデータ処理術

By Sora

更新: 10/17(金) 20:15

cover_1760634772078.png

React Routerのactionとは?データ処理の新しい常識

React Router v6.4以降で導入されたactionは、Webアプリケーションにおけるフォーム送信やデータ変更処理(POST、PUT、PATCH、DELETE)を効率的に扱うための強力な機能です。これまでのReact開発では、フォームのデータ処理はコンポーネント内でState管理やイベントハンドラを使って実装されることが一般的でした。しかし、actionを利用することで、データ変更ロジックをルーティング層に集約し、より宣言的かつ整理されたコードを書くことが可能になります。

actionの主なメリットは以下の通りです。

  • コードの可読性と保守性の向上: データ変更ロジックがルーティング定義に紐づくため、コンポーネントがデータの表示に専念でき、責務が明確になります。
  • 自動的なデータ再検証 (Revalidation): actionが完了すると、関連するloaderが自動的に再実行され、UIに表示されるデータが常に最新の状態に保たれます。これにより、手動でのデータ更新処理が不要になります。
  • 保留中のUI (Pending UI) の管理: フォーム送信中の状態を簡単に検知し、ローディングインジケーターの表示やボタンの無効化といったユーザー体験向上のためのUIを実装できます。

この機能は、Remixフレームワークの思想をReact Routerに取り入れたもので、より強力なデータ管理能力をReactアプリケーションにもたらします。

actionの基本的な使い方:フォーム送信とデータ処理

actionは主に、<Form>コンポーネントからのフォーム送信を処理するために使用されます。基本的な流れは、ルート定義でaction関数をエクスポートし、コンポーネント内で<Form>を使ってデータを送信するというものです。action関数は、フォームから送信されたデータをrequestオブジェクトとして受け取ります。

ルート定義の例

import { createBrowserRouter, RouterProvider, redirect } from 'react-router-dom';

// ユーザー作成ページのアクション
async function createUserAction({ request }) {
  const formData = await request.formData();
  const name = formData.get('name');
  const email = formData.get('email');

  // ここでAPIにデータを送信するなどの処理を行います。
  console.log('ユーザーデータ:', { name, email });

  // 処理が成功したら、別のページにリダイレクトします。
  return redirect('/users');
}

const router = createBrowserRouter([
  {
    path: '/',
    element: <div>Home</div>,
  },
  {
    path: '/users/new',
    element: <NewUserPage />,
    action: createUserAction, // ルートにactionを紐付けます
  },
  {
    path: '/users',
    element: <div>Users List</div>,
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

コンポーネントでのactionの利用

actionの結果は、useActionDataフックを使ってコンポーネント内で受け取ることができます。また、フォーム送信にはReact Routerの<Form>コンポーネントを使用します。

import { Form, useActionData, redirect } from 'react-router-dom';

function NewUserPage() {
  const actionData = useActionData(); // actionからの戻り値を受け取ります

  return (
    <div>
      <h1>新しいユーザーを作成</h1>
      <Form method="post">
        <p>
          <label>名前: <input type="text" name="name" required /></label>
        </p>
        <p>
          <label>メールアドレス: <input type="email" name="email" required /></label>
        </p>
        <button type="submit">作成</button>
      </Form>
      {actionData && actionData.message && <p>{actionData.message}</p>}
    </div>
  );
}

上記の例では、フォームが送信されるとcreateUserActionが呼び出され、redirect関数を使って/usersパスへ遷移します。redirectResponseオブジェクトを返すため、action関数の返り値として直接利用できます。

エラーハンドリングとバリデーション:堅牢なアプリケーションのために

実用的なアプリケーションでは、フォームの入力値検証(バリデーション)やエラーハンドリングが不可欠です。action関数内でこれらの処理を行い、結果をコンポーネントに返すことができます。バリデーションエラーが発生した場合、redirectではなくjson関数を使ってエラー情報を返すのが一般的です。

import { Form, useActionData, json } from 'react-router-dom';

async function validateAndCreateUserAction({ request }) {
  const formData = await request.formData();
  const name = formData.get('name');
  const email = formData.get('email');

  const errors = {};
  if (typeof name !== 'string' || name.length < 3) {
    errors.name = '名前は3文字以上で入力してください。';
  }
  if (typeof email !== 'string' || !email.includes('@')) {
    errors.email = '有効なメールアドレスを入力してください。';
  }

  // エラーがあればJSON形式で返す
  if (Object.keys(errors).length) {
    return json({ errors }, { status: 400 });
  }

  // エラーがなければAPI処理を行い、成功時にリダイレクトなど
  // ... ユーザー作成処理 ...
  return json({ message: 'ユーザーが正常に作成されました。' });
}

function NewUserPageWithValidation() {
  const actionData = useActionData(); // エラー情報を受け取る

  return (
    <div>
      <h1>新しいユーザーを作成 (バリデーション付き)</h1>
      <Form method="post">
        <p>
          <label>名前: <input type="text" name="name" required /></label>
          {actionData?.errors?.name && <p style={{ color: 'red' }}>{actionData.errors.name}</p>}
        </p>
        <p>
          <label>メールアドレス: <input type="email" name="email" required /></label>
          {actionData?.errors?.email && <p style={{ color: 'red' }}>{actionData.errors.email}</p>}
        </p>
        <button type="submit">作成</button>
      </Form>
      {actionData?.message && <p style={{ color: 'green' }}>{actionData.message}</p>}
    </div>
  );
}

// ルート定義でactionを割り当てます
// { path: '/users/new-validated', element: <NewUserPageWithValidation />, action: validateAndCreateUserAction }

actionとloaderの連携:データの一貫性を保つベストプラクティス

actionloaderの連携は、React RouterのデータAPIの最も強力な機能の一つです。actionによってデータが変更されると、自動的にそのルートや関連するルートのloaderが再実行されます。これにより、UIに表示されているデータが常に最新の状態に保たれ、開発者が手動でデータの再取得ロジックを記述する必要がなくなります。

例えば、記事一覧ページで記事の削除actionを実行した場合、そのactionが完了すると記事一覧のloaderが自動的に再実行され、削除された記事が反映された最新の一覧が取得・表示されます。これは、actionがデータ変更操作を、loaderがデータ取得操作を担当するという責務の分離を促進し、コードベースをクリーンに保つ上で非常に有効です。

補足: actionがエラー(例: ステータスコード400や500)を返した場合、デフォルトではloaderの再検証は行われません。これは、エラーレスポンスが必ずしもデータ変更を意味しないためです。必要に応じてshouldRevalidateプロパティを使って再検証の挙動をカスタマイズできます。

UX向上に貢献するuseNavigationフック

フォーム送信のようなデータ変更操作は、完了までに時間がかかることがあります。この間、ユーザーに何もフィードバックがないと、アプリケーションがフリーズしたと誤解されたり、ボタンを何度もクリックしてしまったりと、ユーザー体験が悪化する可能性があります。useNavigationフックは、このような「保留中のナビゲーション」の状態を管理し、より良いUXを提供するために役立ちます。

useNavigationフックは、現在のナビゲーションの状態(idlesubmittingloading)を提供します。

  • idle: ナビゲーションやフォーム送信は行われていません。
  • submitting: <Form>またはuseSubmitによってPOST、PUT、PATCH、DELETEメソッドでフォームが送信中です。action関数が実行されている状態です。
  • loading: 次のページのloaderが呼び出されており、データフェッチ中です。

この状態を利用して、フォーム送信中にボタンを無効化したり、ローディングスピナーを表示したりすることができます。

import { Form, useNavigation } from 'react-router-dom';

function ProductForm() {
  const navigation = useNavigation();
  const isSubmitting = navigation.state === 'submitting';

  return (
    <Form method="post">
      <p>
        <label>商品名: <input type="text" name="productName" required /></label>
      </p>
      <p>
        <label>価格: <input type="number" name="price" required /></label>
      </p>
      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? '追加中...' : '商品を追加'}
      </button>
      {isSubmitting && <p>商品をデータベースに保存しています...</p>}
    </Form>
  );
}

この例では、isSubmittingtrueの間、ボタンが「追加中...」というテキストになり、無効化されます。これにより、ユーザーはフォームが処理中であることを視覚的に理解でき、重複送信を防ぐことができます。これは、React Routerが提供するプログレッシブエンハンスメントの恩恵であり、JavaScriptが無効な環境でも基本的なフォーム機能は動作しつつ、JavaScriptが有効な環境ではよりリッチなユーザー体験が提供されます。

まとめと今後の展望:React Router actionを使いこなす

React Routerのaction機能は、モダンなWebアプリケーション開発におけるデータ変更処理のパラダイムを大きく変えるものです。ルーティング層でデータミューテーションを扱うことで、コンポーネントの責務を明確にし、コードの可読性と保守性を向上させます。また、loaderとの自動的な連携によるデータ再検証は、開発者がデータの同期に頭を悩ませる時間を減らし、より本質的な機能開発に集中できるようになります。

本記事では、actionの基本的な使い方から、エラーハンドリング、バリデーション、そしてuseNavigationフックを活用したUX改善まで、実務で役立つ具体的なコーディングプラクティスを紹介しました。これらの知識を深めることで、より堅牢でユーザーフレンドリーなReactアプリケーションを構築できるはずです。

今後、React RouterはReact 19やサーバーコンポーネントといった新しいWeb技術の進化と連携しながら、さらに強力なデータ管理機能を提供していくことが予想されます。これらの変化に常にアンテナを張り、最新のベストプラクティスを取り入れていくことが、システムエンジニアとして成長していく鍵となるでしょう。公式ドキュメント(React Router Official Documentation)を定期的にチェックし、最新情報をキャッチアップすることをお勧めします。

Opening Comment
どうだったペン?`action`の便利さが伝わったペンか?データ処理のコードがぐっと整理されて、開発効率も上がるはずだペン!これからも、React Routerの機能を活用して、もっと快適な開発ライフを送ってほしいペン!最後まで読んでくれて、ありがとうだペン!