skills/myuon/agent-skills/react-router-loader

react-router-loader

SKILL.md

React Router Loader

画面表示に伴うデータフェッチには loader パターンを使う。データの流れを一方向(loader → コンポーネント)にすることで、コードの見通しが良くなり、不要な useEffect を排除できる。

モードの判別方法

React Router v7 には 3 つのモードがあり、loader の書き方が異なる。

判別ポイント Framework Mode Data Mode Declarative Mode
vite.config.tsreactRouter() プラグイン
createBrowserRouter を使用
<BrowserRouter> を使用

Declarative Mode では loader は使えない。

Data Mode

createBrowserRouter を使うモード。Vite プラグイン不要。

import {
  createBrowserRouter,
  RouterProvider,
  useLoaderData,
} from "react-router";

interface User {
  id: string;
  name: string;
}

const router = createBrowserRouter([
  {
    path: "/users/:id",
    Component: UserPage,
    loader: async ({ params }): Promise<User> => {
      const res = await fetch(`/api/users/${params.id}`);
      if (!res.ok) throw new Response("Not Found", { status: 404 });
      return res.json();
    },
  },
]);

function UserPage() {
  const user = useLoaderData<User>();
  return <div>{user.name}</div>;
}

// エントリポイント
ReactDOM.createRoot(root).render(<RouterProvider router={router} />);

Framework Mode

@react-router/dev の Vite プラグインを使うモード。ファイルベースルーティング・型安全な loader。

./+types/ ディレクトリに自動生成される型を使うことで、loader の戻り値がコンポーネントの props に型安全に渡される。

// app/routes/users.tsx
import type { Route } from "./+types/users";

// Route.LoaderArgs で params, request の型が付く
export async function loader({ params, request }: Route.LoaderArgs) {
  const user = await db.user.findUnique({ where: { id: params.id } });
  if (!user) {
    throw new Response("Not Found", { status: 404 });
  }
  return { user };
}

// Route.ComponentProps により loaderData の型が loader の戻り値から自動推論される
export default function Users({ loaderData }: Route.ComponentProps) {
  const { user } = loaderData;
  return <div>{user.name}</div>;
}

エラーハンドリング

loader で throw した Response は ErrorBoundary でキャッチされる。

export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
  if (isRouteErrorResponse(error)) {
    return <div>{error.status}: {error.data}</div>;
  }
  return <div>Unexpected error</div>;
}

request からの情報取得

export async function loader({ request }: Route.LoaderArgs) {
  const url = new URL(request.url);
  const q = url.searchParams.get("q");           // クエリパラメータ
  const cookie = request.headers.get("Cookie");   // ヘッダー
  return { results: await search(q) };
}

参考リンク

Weekly Installs
1
First Seen
2 days ago
Installed on
amp1
cline1
opencode1
cursor1
kimi-cli1
codex1