MENU
PIXIV FANBOXを開設しています詳しくはこちら!

ド素人には React Router v7 のルーティングが難しすぎたメモ

気が付いたら RemixReact Router v7 に統合されてました。

ということで早速使ってみよう!👈😺ヨシッ!

目次

React Router v7 のインストール

インストールはドキュメントに書かれてますので、適当なディレクトリでインストール!

npx create-react-router@latest my-react-router-app

Gitを初期化する?と聞かれるのでYesを選択。

npmで依存関係をインストールする?と聞かれるのでYesを選択。

完了したらcdでプロジェクトに移動してnpm run devをします!

http://localhost:5173/ を開くと無事、React Router が起動しました!👍🏻👍🏻イェイ!

ルーティング(Routing)をお勉強する

ルーティングとは?

ルーティングとは、超簡単に言うと『ホームページのページを決めてるルール』にあたります。

つまり、ルーティングを理解しないとページが作れないという訳です。重要だね。

ルーティングにちて、詳しい事はドキュメントにあります。

と言いたいのですが、ド素人にはちんぷんかんぷんでした。ので、1つ1つチェックしましょう。

インストール直後

ルーティングが設定されているのはmy-react-router-app/app/routes.tsのファイルになります。

import { type RouteConfig, index } from "@react-router/dev/routes";

export default [index("routes/home.tsx")] satisfies RouteConfig;

ここでは http://localhost:5173/(つまりHTMLにおけるindex.htmlが表示させる状態)を開いたらroutes/home.tsxを表示させる、ということが設定されています。

試しにこの設定を変えてみましょう。

index() について

index()がインデックスの設定になっていそうなので、弄ってみます。

まずはmy-react-router-app/app/home/home.tsxを作成してみます。

export default function Component() {
  return (
    <div className="h-screen flex items-center justify-center">
      <div className="p-6 border border-gray-200 dark:border-gray-700 rounded-3xl">
        New Home
      </div>
    </div>
  );
}

続いて./app/routes.tsを書き換えてみます。

import { type RouteConfig, index } from "@react-router/dev/routes";

export default [
  //index("routes/home.tsx")
  index("home/home.tsx"),
] satisfies RouteConfig;

保存してみると、無事さきほど作成したページが http://localhost:5173/ に表示されました!

でも、タイトルが消えてしまいましたので./app/home/home.tsxを追加調整します。

import type { Route } from "./+types/home";

export function meta({}: Route.MetaArgs) {
  return [
    { title: "New Home" },
    { name: "description", content: "Welcome to React Router!" },
  ];
}

export default function Component() {
  return (
    <div className="h-screen flex items-center justify-center">
      <div className="p-6 border border-gray-200 dark:border-gray-700 rounded-3xl">
        New Home
      </div>
    </div>
  );
}

これでタイトルがちゃんと出ましたね!

route() について

今度は別のページを作ってみましょう。

./app/about/home.tsxを作成します。

import type { Route } from "./+types/home";

export function meta({}: Route.MetaArgs) {
  return [
    { title: "About" },
    { name: "description", content: "Welcome to React Router!" },
  ];
}

export default function Component() {
  return (
    <div className="h-screen flex items-center justify-center">
      <div className="p-6 border border-gray-200 dark:border-gray-700 rounded-3xl">
        About
      </div>
    </div>
  );
}

続いて./app/routes.tsを書き換えてみます。1行目のimportrouteが増えてるので注意!

import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
  //index("routes/home.tsx")
  index("home/home.tsx"),
  route("about", "about/home.tsx"),
] satisfies RouteConfig;

保存してみると、http://localhost:5173/about にさきほど作成したページが表示されました!

route() について(ルートモジュール)

routeはIDを入力されたら、そのIDに対応するページを表示させる。みたいなことができるみたいです。

  route("teams/:teamId", "./teams/home.tsx"),

実際にやってみましょう。

./app/teams/home.tsxを作成します。

import type { Route } from "./+types/home";

function fetchTeam(teamId: string): Promise<{ name: string }> {
  return new Promise((resolve) => {
    setTimeout(() => {
      const name =
        teamId === "tokyo"
          ? "東京チーム"
          : teamId === "sapporo"
          ? "札幌チーム"
          : teamId === "okinawa"
          ? "沖縄チーム"
          : "なし";
      resolve({ name: name });
    }, 2000);
  });
}

export async function loader({ params }: Route.LoaderArgs) {
  let team = await fetchTeam(params.teamId);
  return { name: team.name };
}

export function meta({ params }: Route.MetaArgs) {
  const { teamId } = params;
  return [
    { title: `${teamId} の検索結果` },
    { name: "description", content: "Welcome to React Router!" },
  ];
}

export default function Component({ loaderData }: Route.ComponentProps) {
  return (
    <div className="h-screen flex items-center justify-center">
      <div className="p-6 border border-gray-200 dark:border-gray-700 rounded-3xl">
        {loaderData.name}
      </div>
    </div>
  );
}

そしてに./app/routes.tsを書き換えてみます。

import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
  //index("routes/home.tsx")
  index("home/home.tsx"),
  route("about", "about/home.tsx"),
  route("teams/:teamId", "./teams/home.tsx"),
] satisfies RouteConfig;

http://localhost:5173/teams/ を開くと404になりますが……

http://localhost:5173/teams/tokyo を開くとちゃんと出てきました!

折角なのでmetaloaderで取得した情報を受け渡しましょう。受け取るには{ params, matches }を引数にすればOKです!!

export function meta({ params, matches }: Route.MetaArgs) {
  const project = matches.find((match) => match && match.id === "teams/home")
  const name = (project?.data as { name: string })?.name || "未設定";
  return [
    { title: `${name} の検索結果` },
    { name: "description", content: "Welcome to React Router!" },
  ];
}

これを活用すれば、特定のIDでDBから情報を読み取ってきてOGPにサムネとか設定……みたいなこともできそうですね!便利~~!

route() について(入れ子)

なんとrouteは以下の様に入れ子にもできるそうです。テンプレートとして利用できるイメージですかね。

  route("dashboard", "./dashboard/dashboard.tsx", [
    index("./dashboard/home.tsx"),
    route("settings", "./dashboard/settings.tsx"),
  ]),

実際にやってみましょう。

./app/dashboard/dashboard.tsxを作成します。どうやら<Outlet />に中身が挿入されるようですね。

import { Outlet } from "react-router";

export default function Dashboard() {
  return (
    <div className="h-screen flex items-center justify-center">
      <div className="p-6 border border-gray-200 dark:border-gray-700 rounded-3xl">
        <div className="space-y-6">
          <h5 className="text-xl font-medium">
            Dashboard
          </h5>
          <Outlet />
        </div>
      </div>
    </div>
  );
}

続いてインデックスとなる./app/dashboard/home.tsxを作成します。

import type { Route } from "./+types/home";

export function meta({}: Route.MetaArgs) {
  return [
    { title: "Dashboard" },
    { name: "description", content: "Welcome to React Router!" },
  ];
}

export default function Component() {
  return (
    <div>
      これは Dashboard の Home です
    </div>
  );
}

続けて子のページとなる./app/dashboard/setting.tsxを作成します。

import type { Route } from "./+types/settings";

export function meta({}: Route.MetaArgs) {
  return [
    { title: "Dashboard - Setting" },
    { name: "description", content: "Welcome to React Router!" },
  ];
}

export default function Component() {
  return (
    <div>
      これは Dashboard の Setting です
    </div>
  );
}

最後に./app/routes.tsを書き換えてみます。

import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
  //index("routes/home.tsx")
  index("home/home.tsx"),
  route("about", "about/home.tsx"),
  route("teams/:teamId", "./teams/home.tsx"),
  route("dashboard", "./dashboard/dashboard.tsx", [
    index("./dashboard/home.tsx"),
    route("settings", "./dashboard/settings.tsx"),
  ]),
] satisfies RouteConfig;

http://localhost:5173/dashboard にはhomeが表示されますね!

そして http://localhost:5173/dashboard/settings にはsettingsがちゃんと出ました!

route() について(ルートモジュール&入れ子)

これ合わせ技できそうじゃない?と思って試して見た。

枠となる./app/message/message.tsxを作成

import { Outlet } from "react-router";

export default function Dashboard() {
  return (
    <div className="h-screen flex items-center justify-center">
      <div className="p-6 border border-gray-200 dark:border-gray-700 rounded-3xl">
        <div className="space-y-6">
          <h5 className="text-xl font-medium">
            Message
          </h5>
          <Outlet />
        </div>
      </div>
    </div>
  );
}

indexとなる./app/message/home.tsxを作成

import type { Route } from "./+types/home";

export function meta({}: Route.MetaArgs) {
  return [
    { title: "Message" },
    { name: "description", content: "Welcome to React Router!" },
  ];
}

export default function Component() {
  return (
    <div>
      これは Message の Home です
    </div>
  );
}

検索結果のページとなる./app/message/search.tsxを作成

import type { Route } from "./+types/search";

export async function loader({ params }: Route.LoaderArgs) {
  const { msgId } = params;
  if (msgId == null) return { msg: "" };
  const msg = msgId.split("").reverse().join("");
  return { msg };
}

export function meta({ params }: Route.MetaArgs) {
  const { msgId } = params;
  return [
    { title: `${msgId} の検索結果` },
    { name: "description", content: "Welcome to React Router!" },
  ];
}

export default function Component({ loaderData }: Route.ComponentProps) {
  return <div>{loaderData.msg}</div>;
}

最後に./app/routes.tsを書き換える。

import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
  //index("routes/home.tsx")
  index("home/home.tsx"),
  route("about", "about/home.tsx"),
  route("teams/:teamId", "./teams/home.tsx"),
  route("dashboard", "./dashboard/dashboard.tsx", [
    index("./dashboard/home.tsx"),
    route("settings", "./dashboard/settings.tsx"),
  ]),
  route("message", "./message/message.tsx", [
    index("./message/home.tsx"),
    route(":msgId", "./message/search.tsx"),
  ]),
] satisfies RouteConfig;

http://localhost:5173/message/ ではHomeが出てきた。

http://localhost:5173/message/Hello ではSearchが出てきた。完璧だ!

layout() について

これはrouteの入れ子と同じみたいですね。

違う点はrouteだとパターン(つまりパス)が必ず必要となるが、layoutならパターンが不要なためどこでも使える、みたいな感じかな。たとえば./にてテンプレートが欲しいときはlayoutを、それ以外ではrouteまたはlayoutを利用する想定かな?

  layout("./marketing/layout.tsx", [
    index("./marketing/home.tsx"),
    route("contact", "./marketing/contact.tsx"),
  ]),

route() について(ルートモジュール&入れ子)みたいな事もできそうですよね!

気付いたエラー(ミス)

モジュール ‘./+types/home’ またはそれに対応する型宣言が見つかりません。ts(2307)

型をインポートする際、1行目に

import type { Route } from "./+types/home";

って設定しますが、これって./app/routes.tsにて該当のファイルを紐付けてないと

モジュール ‘./+types/home’ またはそれに対応する型宣言が見つかりません。ts(2307)

というエラーが表示されてしまうので、一瞬焦りますね……

‘loaderData’ は ‘undefined’ の可能性があります。ts(18048)

型のインポートをする際、本来は

import type { Route } from "./+types/home";

のように、そのファイルに合わせて型をインポートしなければならないのに、間違えて

import type { Route } from "../+types/root";

のようにルートなど別ファイルから、型をインポートすると

‘loaderData’ は ‘undefined’ の可能性があります。ts(18048)

という表示がされるので悩んだりもしてました。

ザックリと使い方を理解出来て良かった!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

紅坂 柚月のアバター 紅坂 柚月 自称天才プログラマー

お酒が大好きなバ美肉VTuber。

お出掛けの時は和服を着てます。
プログラムは独学で遊んでます。

目次