はじめに
ウェブアプリケーションでAPIからデータを取得する際、コンポーネントがレンダリングされてからデータ取得が始まると、一瞬画面が空白になる「白い画面」問題が発生します。
React RouterのLoaderは、開発時によく悩まされるデータ取得タイミングの問題を解決してくれます。
Loaderは、ページのレンダリングが始まる前に、必要なデータを準備する役割を担います。
これにより、ユーザーはローディングを待つことなく、データが完全に揃った状態のページを素早く見ることができます。
本記事では、Loaderの基本的な使い方から、データ取得、認証チェックなどの活用方法を解説します。
React Router v7を使用しています。
Loaderの役割とメリット
感覚的には、Expressなんかのミドルウェアに近いです。ページ表示の前に共通処理を挟めるので、コンポーネントがスッキリします。
Loaderの主な役割は以下の3つです。
-
データ取得: APIから必要なデータを取得し、コンポーネントに渡します。これにより、コンポーネントはローディング状態を気にすることなく、データの表示に集中できます。
-
認証チェック: ユーザーがページにアクセスする前に、ログイン済みか、アクセス権限を持っているかを確認します。認証に失敗した場合は、ログインページに自動でリダイレクトします。
-
データの前処理: APIから取得したデータを、コンポーネントに渡す前に加工したり、整形したりします。
Loaderの使い方
ルーティング定義にLoaderを追加する
Loaderは、createBrowserRouterでルーティングを定義する際に、loaderプロパティとして設定します。
// src/router.tsx
import { createBrowserRouter } from "react-router-dom";
import App from "./App.tsx";
import { userLoader } from "./loaders/userLoader.ts"; // Loaderをインポート
import ProfilePage from "./pages/ProfilePage.tsx";
import ErrorPage from "./pages/ErrorPage.tsx";
export const router = createBrowserRouter([
{
path: "/",
Component: App,
errorElement: <ErrorPage />,
children: [
{
path: "profile/:userId",
loader: userLoader, // このルートにLoaderを設定
Component: ProfilePage,
},
],
},
]);
loader関数でデータを取得する
loader関数は、APIを呼び出してデータを取得し、そのデータをreturnします。
// src/loaders/userLoader.ts
import { redirect } from "react-router-dom";
import { getUserProfile } from "../api/userApi.ts"; // APIクライアント
export const userLoader = async ({ params }) => {
try {
// ユーザーIDの存在をチェック
if (!params.userId) {
throw new Error("ユーザーIDが見つかりません");
}
// APIからデータを取得
const user = await getUserProfile(params.userId);
// データを返却
return user;
} catch (error) {
// エラーハンドリング
console.error("ユーザーデータの取得に失敗しました", error);
return redirect("/login");
}
};
コンポーネントでuseLoaderDataを使う
loaderから返されたデータは、コンポーネント内でuseLoaderDataフックを使って取得できます。
// src/pages/ProfilePage.tsx
import { useLoaderData } from "react-router-dom";
function ProfilePage() {
// Loaderから返されたデータを取得
const user = useLoaderData();
return (
<div>
<h1>User Profile</h1>
<p>Name: {user.name}</p>
<p>Email: {user.email}</p>
</div>
);
}
Loaderを使った認証チェック
Loaderは、ページのレンダリング前に認証状態をチェックする「認証ガード」としても使えます。
// src/loaders/checkAuth.ts
import { redirect } from "react-router-dom";
import { useAuthStore } from "../stores/authStore";
export const checkAuthLoader = () => {
const { isAuthenticated } = useAuthStore.getState();
if (!isAuthenticated) {
// 未認証の場合はログインページにリダイレクト
return redirect("/login");
}
return null; // 認証済みの場合は何もしない
};
このLoaderを、認証が必要なルートのloaderに設定することで、認証されていないユーザーがページにアクセスするのを防ぐことができます。
データ共有の効率化
useLoaderDataフックは、データの再取得を防ぎ、パフォーマンスを向上させるための重要な仕組みです。
loaderが返したデータは、ルーティングのメモリ上にキャッシュされます。useLoaderData()フックをコンポーネント内で何度も呼び出しても、Reactはキャッシュされた同じデータを返すため、APIへのリクエストが重複することはありません。
これにより、以下のようなメリットが得られます。
データの重複取得を防止
ProfilePageとUserStatusコンポーネントの両方でuseLoaderData()を呼び出しても、APIリクエストは1回しか行いません。
コンポーネントの再レンダリングを最適化
userPointsのような共通データをuseLoaderData()で取得することで、コンポーネントはデータの変更にのみ反応し、不要な再レンダリングを防ぎます。
おわりに
Loaderを使うようになってから、useEffectや状態管理まわりのコードがかなりスッキリしました。
毎回データ取得のたびにloading状態を気にしなくてよくなるのは、地味だけどかなり助かります。
「ルーティング」と「データの初期化処理」を同じ場所で管理できるのも、個人的には気に入ってるポイントです。
使い慣れてくると、React Routerでのデータ取得の設計がぐっと楽になります。