React Router バージョン6系でloaderの基本を解説します。
例としてTodo一覧と詳細を表示するようなアプリを作ります。
モグモグさん
loaderはv6.4から登場した新しい機能です。
- react 18.2.0
 - react-router-dom 6.6.1
 
モグモグさん
本記事ではTypeScriptを使っています。
使っていない方は、tsxをjsx、型定義を削除するなど適時置き換えていただけますと幸いです。
ルーティングの基本についてはこちらで解説しています。
 
  【React Router v6系】React Routerを導入し基本的なページ遷移を行う方法を解説  
Todo一覧を取得して表示する
まずはルーティングとloaderを定義していきます。
例なので出来るだけシンプルな形で定義していきます。
ルーティング定義とloaderに関数を渡す
import React from "react";
import { createBrowserRouter } from "react-router-dom";
import { ErrorPage } from "./screens/Error";
import { Layout } from "./screens/Layout";
import { TodoList } from "./screens/TodoList";
// Todoの型
export type Todo = {
  id: number;
  title: string;
  completed: boolean;
};
// サンプルデータ(実際はAPIから取得するケースが多い)
const sampleTodoList: Todo[] = [
  { id: 1, title: "Todo 1", completed: false },
  { id: 2, title: "Todo 2", completed: false },
  { id: 3, title: "Todo 3", completed: true },
];
const fetchTodoList = async () => {
  return sampleTodoList;
};
// サンプルデータを返す関数
const loader = async () => {
  return await fetchTodoList();
};
export const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    errorElement: <ErrorPage />,
    children: [{ path: "todo-list", element: <TodoList />, loader: loader }],
  },
]);loaderには関数を渡すことができます。
loaderはページが表示される前に実行されるため、今までuseEffect内などで行っていたことが事前にできるようになります。
それによってユーザー体験の向上が見込めます。
もちろんクライアントサイドで行っていますが、サーバーサイドレンダリングに近いと思います。
loader内では、データを取得しデータを返却します。
ルートファイルでルーティングを定義したファイルを呼び出す
ルーティングを定義したAppRoutesをルートから呼び出します。
モグモグさん
ルートファイルはみなさんの環境に合わせてください!
本記事では、create-react-appを使っているのでsrc/index.tsxがルートファイルになります。
ルーティングを定義したファイルを作成せず、ルートファイルに直接書いてももちろんOKです。
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import reportWebVitals from "./reportWebVitals";
import { RouterProvider } from "react-router-dom";
import { router } from "./components/AppRoutes";
const root = ReactDOM.createRoot(
  document.getElementById("root") as HTMLElement
);
root.render(
  <React.StrictMode>
    <RouterProvider router={router} />
  </React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();loaderで返したデータをページ内で取得する
loader内で返したTodo一覧をTodoListページで取得し表示します。
import { FC } from "react";
import { useLoaderData } from "react-router-dom";
import { Todo } from "../AppRoutes";
export const TodoList: FC = () => {
  const todoList = useLoaderData() as Todo[];
  console.log("data", todoList);
  return (
    <div>
      <h1>Todo List</h1>
      <ul>
        {todoList.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
};useLoaderDataを使うことで、loaderで返したデータが取得できます。

/todo-listにアクセスするとこんな感じでTodo一覧が表示されました!
エラーハンドリング
続いてエラーハンドリングをしていきましょう。
APIを叩いているわけではないので、擬似的にエラーを発生させています。
import React from "react";
import { createBrowserRouter } from "react-router-dom";
import { ErrorPage } from "./screens/Error";
import { Layout } from "./screens/Layout";
import { TodoList } from "./screens/TodoList";
export type Todo = {
  id: number;
  title: string;
  completed: boolean;
};
const sampleTodoList: Todo[] = [
  { id: 1, title: "Todo 1", completed: false },
  { id: 2, title: "Todo 2", completed: false },
  { id: 3, title: "Todo 3", completed: true },
];
export const fetchTodoList = async () => {
  return sampleTodoList;
};
const loader = async () => {
  // 500エラーを返す
  throw new Response("500 Error!", { status: 500 });
  return await fetchTodoList();
};
export const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    errorElement: <ErrorPage />,
    children: [{ path: "todo-list", element: <TodoList />, loader: loader }],
  },
]);loaderでエラーが発生すると、errorElementで渡しているページでエラーを取得できます。
エラーの内容はuseRouteErrorで取得できます。
import { FC } from "react";
import { Link, useRouteError } from "react-router-dom";
export const ErrorPage: FC = () => {
  const error = useRouteError();
  console.log(error);
  return (
    <div>
      <h1>Error!</h1>
      <p>
        <Link to="/">Go back to home.</Link>
      </p>
    </div>
  );
};

500エラーのログが表示されました。
例えばステータスでエラーの内容を変えたりすることができますね。
Todo詳細を取得して表示する
Dynamic Routingの基本についてはこちらで解説しています。
 
  【React Router v6系】React RouterでDynamic Routingを実装する方法を解説  
詳細ページの定義とloader作成
import React from "react";
import { createBrowserRouter, LoaderFunctionArgs } from "react-router-dom";
import { ErrorPage } from "./screens/Error";
import { Layout } from "./screens/Layout";
import { TodoList } from "./screens/TodoList";
export type Todo = {
  id: number;
  title: string;
  completed: boolean;
};
const sampleTodoList: Todo[] = [
  { id: 1, title: "Todo 1", completed: false },
  { id: 2, title: "Todo 2", completed: false },
  { id: 3, title: "Todo 3", completed: true },
];
export const fetchTodoList = async () => {
  return sampleTodoList;
};
const todoListLoader = async () => {
  return await fetchTodoList();
};
const todoDetailLoader = async ({ params }: LoaderFunctionArgs) => {
  const todo = sampleTodoList.find((todo) => todo.id === Number(params.id));
  if (!todo) {
    throw new Response("Todo not found", { status: 404 });
  }
  return todo;
};
export const router = createBrowserRouter([
  {
    path: "/",
    element: <Layout />,
    errorElement: <ErrorPage />,
    children: [
      { path: "todo-list", element: <TodoList />, loader: todoListLoader },
      { path: ":id", element: <TodoDetail />, loader: todoDetailLoader }, // TodoDetailはこの後作成
    ],
  },
]);詳細ページ作成
import { FC } from "react";
import { useLoaderData } from "react-router-dom";
import { Todo } from "../AppRoutes";
export const TodoDetail: FC = () => {
  const todo = useLoaderData() as Todo;
  return (
    <div>
      <h1>{todo.title}</h1>
      <p>status: {todo.completed ? "完了" : "未完了"}</p>
    </div>
  );
};動作確認

/1にアクセスするとこんな感じで詳細ページが表示されます。
/100などの存在しないページにアクセスするとエラーページがレンダリングされます。
まとめ
React Router バージョン6系でloaderの基本を解説しました。
loaderを使ってより良いユーザー体験を目指してみてください。

