React Router バージョン6系でactionの基本を解説します。
例としてTodo作成と作成した一覧を表示するようなアプリを作ります。
モグモグさん
actionはv6.4から登場した新しい機能です。
- react 18.2.0
- react-router-dom 6.6.1
モグモグさん
本記事ではTypeScriptを使っています。
使っていない方は、tsx
をjsx
、型定義を削除するなど適時置き換えていただけますと幸いです。
ルーティングの基本とloaderの基本についてはこちらで解説しています。
【React Router v6系】React Routerを導入し基本的なページ遷移を行う方法を解説
【React Router v6系】React Routerのloaderの基本を解説
Todoを作成して一覧を表示する
まずはルーティングとactionそしてloaderを定義していきます。
例なので出来るだけシンプルな形で定義していきます。
ルーティング、action、loader定義
import React from "react";
import {
ActionFunctionArgs,
createBrowserRouter,
redirect,
} from "react-router-dom";
import { ErrorPage } from "./screens/Error";
import { Layout } from "./screens/Layout";
import { TodoList } from "./screens/TodoList";
// TODOの型定義
export type Todo = {
title: string;
description: string;
};
// localStorageにTODOを保存
const createTodo = async ({ title, description }: Todo) => {
const todoData = await localStorage.getItem("todolist");
const newTodo = {
title: title,
description: description,
};
if (todoData) {
const todoList = JSON.parse(todoData);
todoList.push(newTodo);
await localStorage.setItem("todolist", JSON.stringify(todoList));
} else {
await localStorage.setItem("todolist", JSON.stringify([newTodo]));
}
return newTodo;
};
// localStorageからTODOを取得
const fetchTodoList = async () => {
const todoList = await localStorage.getItem("todolist");
return todoList ? JSON.parse(todoList) : [];
};
// TODOを作成
const action = async ({ request }: ActionFunctionArgs) => {
const formData = await request.formData();
await createTodo({
title: formData.get("title") as string,
description: formData.get("description") as string,
});
return redirect("/todo");
};
// TODO一覧を返す
const loader = async () => {
return await fetchTodoList();
};
export const router = createBrowserRouter([
{
path: "/todo",
element: <Layout />,
errorElement: <ErrorPage />,
children: [
{ index: true, element: <TodoList />, loader: loader, action: action },
],
},
]);
{ index: true, element: , loader: loader, action: action }
/todo
にアクセスするとTodoList
ページが表示されるloader
とaction
をそれぞれ渡している
action
- ページ内のFormがsubmitしたデータを受け取って処理を行う
- actionが完了すると、loaderのデータを再取得してデータの同期を行う(ここが便利ポイント)
loader
- todo一覧を取得して返している
ルートファイルでルーティングを定義したファイルを呼び出す
ルーティングを定義した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();
TodoListページを作成する
TodoListページを作成しましょう。
Formでsubmitしたデータが上で説明したactionの中で受け取れます。
import { FC } from "react";
import { Form, useLoaderData } from "react-router-dom";
import { Todo } from "../AppRoutes";
export const TodoList: FC = () => {
const todos = useLoaderData() as Todo[];
return (
<div>
<h1>Todo List</h1>
<ul>
{todos.map((todo) => (
<li key={todo.title}>
{todo.title}: {todo.description}
</li>
))}
</ul>
<h2 style={{ marginTop: "20px" }}>New Todo</h2>
<Form method="post" id="contact-form">
<p style={{ marginTop: "10px" }}>
<input
placeholder="title"
aria-label="title"
type="text"
name="title"
/>
</p>
<p style={{ marginTop: "10px" }}>
<input
placeholder="description"
aria-label="description"
type="text"
name="description"
/>
</p>
<button
type="submit"
style={{
marginTop: "15px",
border: "1px solid #ccc",
padding: "10px",
}}
>
Save
</button>
</Form>
</div>
);
};
/todo
にアクセスするとこんな感じで動作します。
エラーが発生した場合は、errorElement
で受け取ることが可能です。
まとめ
React Router バージョン6系でactionの基本を解説しました。
loaderとactionの基本を押さえればReact Routerの形に則ってCRUDの処理ができるようになると思います。
参考になれば幸いです。