サイト名変更・お引越しのお知らせ

【React Router v6系】React Routerのactionの基本を解説

React Router バージョン6系でactionの基本を解説します。

例としてTodo作成と作成した一覧を表示するようなアプリを作ります。

モグモグさん

actionはv6.4から登場した新しい機能です。

バージョン
  • react 18.2.0
  • react-router-dom 6.6.1

モグモグさん

本記事ではTypeScriptを使っています。

使っていない方は、tsxjsx、型定義を削除するなど適時置き換えていただけますと幸いです。

ルーティングの基本と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ページが表示される
    • loaderactionをそれぞれ渡している
  • 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リスト

/todoにアクセスするとこんな感じで動作します。

エラーが発生した場合は、errorElementで受け取ることが可能です。

まとめ

React Router バージョン6系でactionの基本を解説しました。

loaderとactionの基本を押さえればReact Routerの形に則ってCRUDの処理ができるようになると思います。

参考になれば幸いです。