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を使ってより良いユーザー体験を目指してみてください。