React で簡単にルーティングできる Router ライブラリの使い方【 TypeScript 】

ショウヘイ

どうも、ノマドクリエイターのショウヘイ(@shohei_creator)です。

ふーちゃん

ブログアシスタントのふーちゃんです。

React は SPA のため、通常の Web サイトのように『 URL ごとに、個別の HTML ファイルをサーバーから配信する』という挙動が出来ません。 URL ごとに別ページを表示させているように見せるには、 URL に対応したコンポーネントを出し分けするルーティング処理の実装が必要です。

React のライブラリの 1 つである Router を導入すると、 React で簡単にルーティング処理を実装できます。

この記事では、 React ライブラリの Route の使い方について説明します。

ショウヘイ

ちなみに、この記事の情報は、 Router のバージョン 6 に基づいてに基づいています。

バージョン 5 以前と比べると、タグやメソッドの使い方に変更が発生しています。注意してください。

目次

React の Router ライブラリとは

Router は、 React でルーティング処理を簡単に実装できるようになるライブラリです。 React 向けのルーティング系のライブラリとしては、現在のところ、最も人気が高いです。

あわせて読みたい
Declarative routing for React apps at any scale | React Router Version 6 of React Router is here! React Router v6 takes the best features from v3, v5, and its sister project, Reach Router, in our smallest and most powerful ...

React の Router の環境構築

React で Router 使うには、まずは npm または yarn などの JavaScript パッケージマネージャーを利用して、 Router ライブラリをインストールします。

React でアプリ開発するフォルダのディレクトリにて、下記のコマンドをターミナルで実行してください。

// npmを使う場合
npm add react-router-dom@6

// yarnを使う場合
yarn install react-router-dom@6

これから、 React アプリのファイルを編集していきます。まだ手元に編集するアプリを用意していないなら、 create-react-app コマンドと使って、任意のアプリ名で初期の環境構築をおこなってください。

// 通常版
npx create-react-app アプリ名 

// TypeScript対応版
npx create-react-app アプリ名 --template typescript

Router を使えるようにしたいコンポーネントが書かれているファイルを開きます。基本的には、 App コンポーネントが書かれている index.tsx ファイルを開けばいいでしょう。

まずは、 react-rouer-dom ライブラリから、 BrowserRouter を import します。そして、 BrowserRouter タグで App コンポーネントタグを囲みます。

これにより、 App コンポーネント以下の下層コンポーネントでは、 Router のルーティング機能を使えるようになります。

import { render } from "react-dom";
import App from "./App";

// react-router-domからBrowserRouterをimportする。
import { BrowserRouter } from "react-router-dom";

render(
  // BrouserRouerタグでAppタグを囲む。
  <BrowserRouter>
    <App />
  </BrowserRouter>,
  document.getElementById("root")
);
ショウヘイ

Router の Router タグは、 BrowserRouter ・ HashRouter ・ NativeRouter ・ MemoryRouter の 4 種類が用意されています。 

Web アプリ向けに Router を使うなら、基本的には BrowserRouter を選べば OK です。

今度は、 App.tsx ファイルを開きます。

 まずは、 react-rouer-dom ライブラリから、 Routes ・ Route ・ Link を import します。 そして、ルーティング処理をしたい場所に Routes タグを書いて、その内部に Route タグを適当に書き並べておきます。

本来は、 Route タグの path 属性と element 属性の設定が必要になりますが、詳細は後に譲ります。ひとまず、これでルーティング処理を実装するための環境構築は終わりです。

import { Routes, Route, Link } from "react-router-dom";

export default function App() {
  return (
    <Routes>
      <Route path="/" element={} />
      <Route path="/" element={} />
      <Route path="/" element={} />
    </Routes>
  );
}

React の Router で基本になるルーティング設定

ショウヘイ

Router のルーティング機能を使う時に、基本的に使うことになる Route タグと Link タグについて、あわせて説明します。

ふーちゃん

リンク系のタグとしては、アクティブリンクの表示に使える NavLink タグもあります。 

Link タグと一緒に、 NavLink タグの使い方も把握しておいてくださいね。

Router の Route タグの設定( path 属性・ element 属性)

Router を使ってルーティングするために、まずは Route タグの path 属性と element 属性に、適切な値を設定します。そのために、 URL によって出し分けるコンポーネントを作ります。

この具体例では、 src フォルダの下に components フォルダを作り、その中に 3 つの tsx ファイル( About ・ Contact ・ Home )を用意しました。

src
├ components
│   ├ About.tsx
│   ├ Contact.tsx
│   └ Home.tsx
│
├ App.tsx
└ index.tsx

3つの tsx ファイル の中身は、ほぼ同じです。

import React from "react";

export const About: React.VFC = () => {
  return <h1>ABOUT</h1>;
  );
};
import React from "react";

export const Contact: React.VFC = () => {
  return <h1>CONTACT</h1>;
  );
};
import React from "react";

export const Home: React.VFC = () => {
  return <h1>HOME</h1>;
};

App.tsx ファイルにて、先ほど作った 3 つのコンポーネントを import します。そして、 Route タグの element 属性の値として、コンポーネントタグを設定します。

あわせて、 path 属性の値も設定しておきます。 Home コンポーネントはルートディレクトリの「 / 」、 About コンポーネントは「 /about 」、 Contact コンポーネントは「 /contact 」としておきました。

import { Routes, Route, Link } from "react-router-dom";
import { Home } from "./components/Home";
import { About } from "./components/About";
import { Contact } from "./components/Contact";

export default function App() {
  return (
    <Routes>
    // path属性とelement属性をコンポーネントごとに設定 
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="/contact" element={<Contact />} />
    </Routes>
  );
}

ここまで書き終えると、ページに「 HOME 」と表示された状態になります。

React で Router の Route タグの path 属性と element 属性の設定が終わった状態のページ

ブラウザの URL の末尾に「 about 」を追加して、 Enter キーを押してみてください。ルーティングが機能して、「 HOME 」を表示していたコンポーネントがアンマウントされて、代わりに「 ABOUT 」を表示するコンポーネントがマウントします。

Router の Link タグの設定( to 属性)

Router を使ってルーティングするには、 Route タグの path 属性の値に合わせて、 URL を書き換える必要があります。 Router には、 HTML ページの a タグのように、クリックによって URL を書き換えるための Link タグが用意されています。

Routes タグの上に、ナビゲーションのためのコードを追記します。 Link タグを並べるだけでも機能しますが、セマンティック HTML に配慮して、具体例では nav タグ・ ul タグ・ li タグも併用しています。

Link タグの props である to 属性の値には、書き換え後の URL を設定します。また、リンク化したい要素を Link タグで囲みます。

ちなみに、 React の render メソッドは、 1 つの要素しか返り値に出来ない仕様があります。そこで、具体例の nav タグと Routes タグを Flagment タグで囲んでいます。

import { Routes, Route, Link } from "react-router-dom";
import { Home } from "./components/Home";
import { About } from "./components/About";
import { Contact } from "./components/Contact";

export default function App() {
  return (
  // Flagmentタグの追加
    <>
   // nav・ul・li・Linkタグの追加
      <nav>
        <ul>
          <li><Link to="/">HOME</Link></li>
          <li><Link to="/about">ABOUT</Link></li>
          <li><Link to="/contact">Contact</Link></li>
        </ul>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </>
  );
}
ショウヘイ

ここまでの実装を終わらせた CodeSandbox を用意しました。

ナビゲーションのリンクをクリックしてみてください。

動的に URL が変わり、ルーティングによるコンポーネントの出し分けが起きることを確認できます。

Router で NavLink タグによるアクティブリンクを実装する方法

React の Router ライブラリには、現在の URL とリンクの path 属性の値が一致している(リンクがアクティブ状態)ことを明示するための特別なタグとして、 NavLink タグが用意されています。

NavLInk タグは、 現在の URL とリンクの path 属性の値が一致していると、 isActive が true に切り替わります。この仕様を利用して、 style 属性や className 属性に isActive を引数とする関数を設定します。あとは、三項演算子などを組み合わせれば、 CSS によるアクティブ強調がおこなえます。

// style属性を使った場合
<NavLink to="path" style={({ isActive }) => (isActive ? activeStyle : {})}

// className属性を使った場合
<NavLink to-"path" className={({ isActive }) => (isActive ? "activated" : "")} 
ショウヘイ

NavLink タグの仕様が分かりやすいように、 App.tsx ファイルの中身を改変しました。ついで、 styles.css ファイルも追加しています。

リンクがアクティブ状態( URL と to 属性の値が一致)の場合は、 isActive が true になり、アクティブリンク専用の CSS が適用されるようになっています。

.activated {
  color: green;
  font-weight: bold;
  background-color: yellow;
}
// react-router-domから、NavLinkをimportする
import { Routes, Route, Link, NavLink } from "react-router-dom";
import { Home } from "./components/Home";
import { About } from "./components/About";
import { Contact } from "./components/Contact";
// アクティブリンク用のクラスを定義したcssファイルをimportする
import "./styles.css";

export default function App() {
  // styleを定義した変数に対する型宣言
  const activeStyle: React.CSSProperties = {
    color: "red",
    fontWeight: "bold",
    backgroundColor: "blue"
  };

  return (
    <>
      <nav>
        <ul>
          <li>
            {/* 通常のLinkタグ */}
            <Link to="/">HOME</Link>
          </li>
          <li>
            {/* Linkタブではなく、NavLinkタグを使う */}
            <NavLink
              to="/about"
              // isActiveを引数にして、アロー関数を書く。
              // isActiveで三項演算子を使い、trueの場合にstyleが適用されるようにする。
              style={({ isActive }) => (isActive ? activeStyle : {})}
            >
              ABOUT
            </NavLink>
          </li>
          <li>
            {/* Linkタブではなく、NavLinkタグを使う */}
            <NavLink
              to="/contact"
              // isActiveを引数にして、アロー関数を書く。
              // isActiveで三項演算子を使い、trueの場合にclass名が適用されるようにする。
              className={({ isActive }) => (isActive ? "activated" : "")}
            >
              CONTACT
            </NavLink>
          </li>
        </ul>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </>
  );
}

React の Router で 404 ページを表示する Route タグの設定

Web サイトでは、参照先のファイルが存在しない URL が参照された場合を想定して、 404 ページを用意しておくことが慣例になっています。 React の Router ライブラリでも、同様の実装が可能です。

Router の Route タグでは、すべてのパスにヒットさせられるワイルドカード「 * 」が用意されています。このワイルドカードを使いつつ、 element 属性に 404 エラー用のコンポーネントを設定することで、さながら 404 ページのような挙動になります。

ショウヘイ

404 ページの実装の具体例として、あえて Route タグのパスに対応しない Link タグをいくつか用意しました。

実際にリンクをクリックしてみて、 404 エラー用の NotFound コンポーネントが表示されることを確認してみてください。

src
├ components
│   ├ About.tsx
│   ├ Contact.tsx
│   ├ Home.tsx
│   └ NotFound.tsx
│
├ App.tsx
└ index.tsx
import React from "react";

export const NotFound: React.VFC = () => {
  return (
    <>
      <h1>404 not found</h1>
      <p>リクエストされたページは見つかりませんでした。</p>
    </>
  );
};
import { Routes, Route, Link } from "react-router-dom";
import { Home } from "./components/Home";
import { About } from "./components/About";
import { Contact } from "./components/Contact";
// 404エラー用のNotFoundコンポーネントをimport
import { NotFound } from "./components/NotFound";

export default function App() {
  return (
    <>
      <nav>
        <ul>
          <li><Link to="/">HOME</Link></li>
          <li><Link to="/about">ABOUT</Link></li>
          <li><Link to="/contact">CONTACT</Link></li>
          {/* Routeタグのpathに対応しないLinkタグ */}
          <li><Link to="/profile">PROFILE</Link></li>
          <li><Link to="/sns">SNS</Link></li>
          <li><Link to="/blog">BLOG</Link></li>
        </ul>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        {/* path属性にワイルドカード「*」を設定 */}
     {/* element属性にNotFoundコンポーネントを設定 */}
        <Route path="*" element={<NotFound />} />
      </Routes>
    </>
  );
}

React の Router で入れ子になった Route タグの設定

React でルーティングする時に、複数の階層に渡ってコンポーネントを表示させて、かつ子階層のコンポーネントを出し分けたいこともあるでしょう。

複数の階層に渡るルーティングを実装するには、 Route タグの中に別の Route タグを設置する入れ子の構造を使います。また、入れ子の親コンポーネントに Outlet タグを設置します。

ショウヘイ

具体例として、『ユーザー情報を出し分けるようにルーティングするコード』を用意しました。まずは、完成形のアプリ画像を載せておきます。

なお、階層の違いが分かりやすくなるように、 Bootstrap で CSS 修飾を施しています。

React の Router ライブラリを使った入れ子構造のルーティングの具体例

具体例は、以下のようなフォルダ・ファイル構成になっています。

src
├ components
│   ├ Tanaka.tsx
│   ├ User.tsx
│   └ Yamada.tsx
│
├ App.tsx
└ index.tsx

まずは、 App.tsx ファイルの中身を改変します。

入れ子にする子側の Route タグの path 属性は、親側の Route タグから見た相対パスを設定します。今回の場合は、親側の Route タグの path 属性の値が「 / 」なので、子側の Route タグの path 属性の値を「 / 」から書く必要はありません。

import { Routes, Route, Link } from "react-router-dom";
import { Container } from "react-bootstrap";
// ユーザーに関するコンポーネントをimportする
import { User } from "./components/User";
import { Yamada } from "./components/Yamada";
import { Tanaka } from "./components/Tanaka";


export default function App() {
  return (
    <Container className="mt-4">
      <nav>
        <ul>
           {/* Linkタグのto属性と子要素の文言を変更 */}
          <li><Link to="/">User</Link></li>
          <li><Link to="/yamada">Yamada</Link></li>
          <li><Link to="/tanaka">Tanaka</Link></li>
        </ul>
      </nav>
      <Routes>
        {/* Routeタグの内部にRouteタグを設置して、入れ子構造にする */}
        <Route path="/" element={<User />}>
          {/* 子側のRouteのpath属性は、親のRouteタグのpathから見た相対パスを設定する */}
          <Route path="yamada" element={<Yamada />} />
          <Route path="tanaka" element={<Tanaka />} />
        </Route>
      </Routes>
    </Container>
  );
}

次に、 User.tsx ファイルを作り、入れ子構造の親コンポーネントとなる User コンポーネントを用意します。特筆すべき点は、 Outlet タグを設置していることです。

Outlet タグは、 App.tsx ファイルで入れ子になっている Route タグのうち、子側の Route タグの element 属性で設定されているコンポーネントを差し込むための目印として機能します。

import React from "react";
import { Alert } from "react-bootstrap";
// react-router-domライブラリから、Outletをimportする
import { Outlet } from "react-router-dom";


export const User: React.VFC = () => {
  return (
    <Alert variant="primary">
      <h1>User</h1>
      {/* Outletタグを設置 */}
      {/* 子側のRouteタグのelement属性のコンポーネントが差し込まれる目印になる */}
      <Outlet />
    </Alert>
  );
};

あとは、子側の Route タグで使っている Yamada タグと Tanaka タグを用意するだけです。コードをまとめて紹介します。

import React from "react";
import { Alert } from "react-bootstrap";

export const Yamada: React.VFC = () => {
  return (
    <Alert variant="success">
      <ul>
        <li>名前:山田太郎</li>
        <li>性別:男</li>
        <li>年齢:25歳</li>
      </ul>
    </Alert>
  );
};
import React from "react";
import { Alert } from "react-bootstrap";

export const Tanaka: React.VFC = () => {
  return (
    <Alert variant="success">
      <ul>
        <li>名前:田中花子</li>
        <li>性別:女</li>
        <li>年齢:20歳</li>
      </ul>
    </Alert>
  );
};
ショウヘイ

ここまでの実装を終わらせた CodeSandbox を用意しました。

ナビゲーションのリンクをクリックしてみてください。 

User.tsx ファイルで Outlet タグを挿入した部分に、『子側の Route タグの elemet 属性に設定したコンポーネント』が差し込まれることを確認できます。

 ルートディレクトリ「 / 」でも Outlet タグを機能させる( index 属性)

Router ライブラリの Outlet タグは、 URL の階層構造に従って、段々と下層コンポーネントを表示する仕様です。そのため、『ルートディレクトリ「 / 」の時に、特定の下層コンポーネントを Outlet タグの部分に表示させる』という場合には、不都合が起きてしまいます。

上記の問題が解消するためには、 elemet 属性に下層コンポーネントを持つ Route タグに対して、特別に index 属性をつけます。すると、ルートディレクトリの場合でも、 index 属性を持つ Route タグがルーティング対象になります。

ショウヘイ

先ほどの具体例を下地にして、 index 属性を持つ Route タグを追加しました。あわせて、ルートディレクトリで表示する Default コンポーネントも追加しています。 

index 属性により、子側の Route タグがルートディレクトリでも表示されていることを確認してください。

ふーちゃん

ちなみに、 index 属性には、わざわざ「 true 」と書く必要はありません。 

React では、タグに属性名だけが書いてある場合には、属性の値が true として扱われるからです。

import { Routes, Route, Link } from "react-router-dom";
import { Container } from "react-bootstrap";
import { User } from "./components/User";
import { Yamada } from "./components/Yamada";
import { Tanaka } from "./components/Tanaka";
// ルートディレクトリで表示されるDefaultコンポーネントをimport
import { Default } from "./components/Default";

export default function App() {
  return (
    <Container className="mt-4">
      <nav>
        <ul>
          <li><Link to="/">User</Link></li>
          <li><Link to="/yamada">Yamada</Link></li>
          <li><Link to="/tanaka">Tanaka</Link></li></ul>
      </nav>
      <Routes>
        <Route path="/" element={<User />}>
          {/* index属性をつける代わりに、path属性はつけない */}
          <Route index element={<Default />} />
          <Route path="/yamada" element={<Yamada />} />
          <Route path="/tanaka" element={<Tanaka />} />
        </Route>
      </Routes>
    </Container>
  );
}
import React from "react";
import { Alert } from "react-bootstrap";

export const Default: React.VFC = () => {
  return (
    <Alert variant="success">
      <ul>
        <li>名前:</li>
        <li>性別:</li>
        <li>年齢:</li>
      </ul>
    </Alert>
  );
};

React の Router で リダイレクトする方法( useNavigate フック)

React の Router ライブラリを使ってリダイレクト処理を実装する場合には、 useNavigate フックを使います。

Router の useNavigate フックを使うと、 navitgate メソッドを生成できます。 navigate メソッド には、 2 つの引数を渡せます。第 1 引数は、リダイレクト先の URL (相対パスで指定)です。第 2 引数は、 state と replace をまとめたオブジェクトです。

 第 2 引数の state には、リダイレクト先のコンポーネントに渡したい変数を設定できます。 state の値をオブジェクトにすれば、複数の変数をまとめて渡せます。

 第 2 引数の replace には、リダイレクト元ページの参照を履歴に残すかどうかの真偽値を渡せます。 true の場合には、参照を履歴に残しません。 false の場合には、参照を履歴に残すので、ブラウザの戻るボタンをクリックするなどすれば、リダイレクト元のページが閲覧できます。

// react-router-domから、useNavigateフックをimportする。
import { useNavigate } from "react-router-dom";

// いったん、navigateメソッドを生成する。
const navigate = useNavigate();

// 第1引数には、リダイレクト先の相対パスを指定。
// 第2引数には、必要に応じてstateとreplaceを指定。
navigate("relative path", { state: { key1: value1, key2: value2, key3: value3 }, replace: booleran });
ショウヘイ

useNavigate フックの仕様を理解しやすいように、ログインフォームのような具体例を用意しました。 

replate に true を渡す場合・ false を渡す場合の 2 通りのフォームを作ったので、ブラウザの戻る操作が出来るかどうかの挙動を確認してください。

ふーちゃん

リダイレクト先のコンポーネントに渡した state は、 useLocation フックを使うことで取り出せますよ!

src
├ components
│   ├ Home.tsx
│   ├ Redirected.tsx
│
├ App.tsx
└ index.tsx
import { Routes, Route } from "react-router-dom";
import { Container } from "react-bootstrap";
import { Home } from "./components/Home";
import { Redirected } from "./components/Redirected";

export default function App() {
  return (
    <Container>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/redirected" element={<Redirected />} />
      </Routes>
    </Container>
  );
}
import React, { useState } from "react";
import { Alert } from "react-bootstrap";
import { useNavigate } from "react-router-dom";

export const Home: React.VFC = () => {
  const [nameT, setnameT] = useState<string>("");
  const [nameF, setnameF] = useState<string>("");
  const navigate = useNavigate();

  const trueSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    navigate("/redirected", { state: { name: nameT }, replace: true });
  };

  const falseSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    navigate("/redirected", { state: { name: nameF }, replace: false });
  };

  const trueChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    setnameT(e.target.value);
  };
  const falseChangeInput = (e: React.ChangeEvent<HTMLInputElement>) => {
    setnameF(e.target.value);
  };

  return (
    <>
      <h1>HOME</h1>
      <Alert variant="primary">
        <p>replace=true</p>
        <p>(リダイレクト元の履歴が残らない)</p>
        <form onSubmit={trueSubmit}>
          <legend style={{ marginBottom: "10px" }}>
            ユーザー名
            <input
              type="text"
              style={{ marginLeft: "5px" }}
              onChange={(e) => trueChangeInput(e)}
            />
          </legend>
          <button>ログインする</button>
        </form>
      </Alert>
      <Alert variant="success">
        <p>replace=false</p>
        <p>(リダイレクト元の履歴が残る)</p>
        <form onSubmit={falseSubmit}>
          <legend style={{ marginBottom: "10px" }}>
            ユーザー名
            <input
              type="text"
              style={{ marginLeft: "5px" }}
              onChange={(e) => falseChangeInput(e)}
            />
          </legend>
          <button>ログインする</button>
        </form>
      </Alert>
    </>
  );
};
import React from "react";
// リダイレクト時のstateを取得するために、useLocationフックをimportする
import { Link, useLocation } from "react-router-dom";

export const Redirected: React.VFC = (props) => {
  // useLocationフックを使い、stateを分割代入で取り出す
  const { state } = useLocation();

  return (
    <>
      <Link to="/">HOMEに戻る</Link>
      <h1>Redirected</h1>
      {/* stateの中にあるnameプロパティを参照する */}
      <p>ユーザー名:{state?.name ? state.name : null}</p>
    </>
  );
};

React の Router で URL パラメーターを取得する方法( useParams フック)

React でルーティングする時に、 URL の一部( URL パラメーター)を取得して、コンポーネントに渡して使いたい場合があります。

このような実装をおこなうには、 Route タグの path 属性で「 : 」から始まる文字列を設定します。また、 URL パラメーターを受け取る側のコンポーネントにて、 useParams フックを使います。

ショウヘイ

具体例として、『ユーザー情報が格納された json ファイルから、 URL パラメーターに対応するユーザー情報を取得するコード』を用意しました。

まずは、完成形のアプリ画像を載せておきます。

React の Router ライブラリで URL パラメーターを取り出す具体例

具体例は、以下のようなフォルダ・ファイル構成になっています。

src
├ components
│   └ User.tsx
│
├ data
│   └ users.ts
│
├ App.tsx
└ index.tsx

まずは、 App.tsx ファイルの中身を改変します。

import { Routes, Route, Link } from "react-router-dom";
import { Container } from "react-bootstrap";
// ユーザーに関するコンポーネントをimportする
import { User } from "./components/User";

export default function App() {
  return (
    <Container className="mt-4">
      <nav>
        <ul>
          {/* Linkタグのto属性は、「users/id」という構成にしておく */}
          <li><Link to="/">TOP</Link></li>
          <li><Link to="/users/1">ユーザー1</Link></li>
          <li><Link to="/users/2">ユーザー2</Link></li>
          <li><Link to="/users/3">ユーザー3</Link></li>
          <li><Link to="/users/4">ユーザー4</Link></li>
          <li><Link to="/users/5">ユーザー5</Link></li>
        </ul>
      </nav>
      <Routes>
        {/* Routeタグのpath属性は、idの数字を取得する部分を「:id」としておく */}
        <Route path="/users/:id" element={<User />} />
      </Routes>
    </Container>
  );
}

次に、 data フォルダの下に、ユーザー情報をまとめて書いておく users.ts ファイルを作ります。ファイルの中には、オブジェクトを要素に持つ配列を定義して、 export しておきます。

export const users = [
  { id: 1, name: "山田太郎", gender: "男性", age: 25 },
  { id: 2, name: "田中花子", gender: "女性", age: 20 },
  { id: 3, name: "鈴木勝也", gender: "男性", age: 30 },
  { id: 4, name: "佐藤里香", gender: "女性", age: 15 },
  { id: 5, name: "斎藤佑樹", gender: "男性", age: 35 }
];

最後に、 User.tsx ファイルを作り、 User コンポーネントを用意します。

まずは、 react-router-dom ライブラリから、 useParams フックを import します。さらに、 users.ts ファイルから users も impot しておきます。

次に、 useParams フックを使って、 URL パラメーターの参照を params に格納しておきます。そして、 params の中にある id を参照して、 URL の「 :id 」に対応する文字列を取得して、 userId に格納します。なお、数字型として扱いたいので、 Number メソッドで params.id を囲んで型変換します。

最後に、ユーザー情報が配列で格納されている uses に対して、インデックスとして useId を使います。ちなみに、配列のインデックスは 0 から始まる都合により、 users[userId – 1]と書いています。

import React from "react";
// useParamsフックとusersをimoportする
import { useParams } from "react-router-dom";
import { users } from "../data/users";

export const User: React.VFC = () => {
  // useParamsフックを使い、URLパラメータの参照をparamsに格納
  const params = useParams();
  // paramsの中にあるidプロパティを参照
  const userId: number = Number(params.id);
  // users配列でインデックスを指定して、ユーザー情報を取得
  const user = users[userId - 1];

  return (
    <>
      <h1>User</h1>
      <ul>
        {/* userオブジェクトの中にあるプロパティを列記 */}
        <li>ID:{user.id}</li>
        <li>名前:{user.name}</li>
        <li>性別:{user.gender}</li>
        <li>年齢:{user.age}歳</li>
      </ul>
    </>
  );
};
ショウヘイ

ここまでの実装を終わらせた CodeSandbox を用意しました。

ナビゲーションのリンクをクリックしてみてください。 

URL の「 :id 」に相当する数字によって、コンポーネントに表示されるユーザー情報が切り替わることが確認できます。

React の Router でクエリパラメータを取得する方法( useSearchParams フック)

React の Router ライブラリには、 URL のクエリパラメーターを取得できる useSearchParams フックが用意されています。 

useSearchParams フックを使うと、『クエリパラメーターが格納されている searchParams オブジェクト』と『クエリパラメーターを更新できる setter メソッド』の 2 つが生成されます。

クエリパラメーターから値を取得するには、 searchParams の get メソッドに key を渡します。

// react-router-domライブラリから、useSearchParamsフックをimportする
import { useSearchParams } from "react-router-dom";

// searchParamsオブジェクトとsetterメソッドを生成する
const [searchParams, setSearchParams] = useSearchParams();

// searchParamsオブジェクトのgetメソッドにkeyを渡す
// クエリパラメーターが「?id=12345」なら、param には「12345」が格納される
const param = searchParams.get("id")
ショウヘイ

Router の useSearchParams フックの使い方の具体例として、簡単な検索フォームを用意しました。 

Form コンポーネントでは、検索フォームの入力内容に基づいて、クエリパラメーターつきの path を生成して、リダイレクトによって URL を書き換えます。 

SearchResult コンポーネントでは、クエリパラメーターから値を取り出して、 filter メソッドによって条件に合致するデータを絞り込む仕様です。

React の Router の useSearchPrams フックを使った検索フォーム

具体例は、以下のようなフォルダ・ファイル構成になっています。

src
├ components
│   ├ Form.tsx
│   └ SearchResult.tsx
│
├ data
│   └ users.ts
│
├ styles
│   └ form.ts
│
├ types
│   └ User.ts
│
├ App.tsx
└ index.tsx

まずは、ユーザーオブジェクトの型を定義しておくための User.ts ファイルです。

export type User = {
  id: number;
  name: string;
  gender: string;
  score: number;
};

SearchResult コンポーネントで使う予定のユーザー情報を定義した users.ts ファイルです。

import { User } from "../types/User";

export const users: User[] = [
  { id: 1, name: "山田太郎", gender: "男性", score: 100 },
  { id: 2, name: "田中花子", gender: "女性", score: 95 },
  { id: 3, name: "鈴木勝也", gender: "男性", score: 82 },
  { id: 4, name: "佐藤里香", gender: "女性", score: 77 },
  { id: 5, name: "斎藤佑樹", gender: "男性", score: 58 },
  { id: 6, name: "近藤達也", gender: "男性", score: 54 },
  { id: 7, name: "早川美紀", gender: "女性", score: 49 },
  { id: 8, name: "江田早苗", gender: "女性", score: 42 },
  { id: 9, name: "富田俊樹", gender: "男性", score: 28 },
  { id: 10, name: "市川泰三", gender: "男性", score: 22 }
];

App.tsx ファイルです。 2 つの Route タグに、それぞれ Form コンポーネントと SearchResult コンポーネントを設定しています。

SearchResult コンポーネントを設定した Route タグについては、クエリパラメーターを追加して「 /users?name= …」のような形式になるので、ワイルドカード「 * 」を使って対応させます。

import { Routes, Route } from "react-router-dom";
import { Container } from "react-bootstrap";
import { Form } from "./components/Form";
import { SearchResult } from "./components/SearchResult";

export default function App() {
  return (
    <Container className="mt-4">
      <Routes>
        <Route path="/" element={<Form />} />
        {/* クエリパラメーターを使うので、ワイルドカードを使う */}
        <Route path="/users/*" element={<SearchResult />} />
      </Routes>
    </Container>
  );
}

Form.tsx ファイルです。それぞれの入力欄が見やすくなるように、簡単な css ファイルを適用しています。

クエリパラメーターを追加した URL の生成は、 useSearchParams フックによって生成した navigate オブジェクトを利用しています。

createSearchParams メソッドは、クエリパラメーターオブジェクトの生成に便利なメソッドです。引数としてオブジェクトを渡すことで、クエリパラメーターに特有の「? query1=value1&query2=value2&query3=value3 」の形式を簡単に作れます。

submit イベントを経由して得られる HTMLFormElement は、 currentTarget に続けて name 属性と value を付加すると、フォームの入力値・選択値を取得できます。

import React from "react";
import "../styles/form.css";
// react-router-domライブラリから、useSearchParamsフックをimportする
// また、クエリパラメーターの生成に使うcreateSearchParamsメソッドもimportする
import { useNavigate, createSearchParams } from "react-router-dom";

export const Form: React.VFC = () => {
  // useNavigateフックを使って、navigateメソッドを生成する
  const navigate = useNavigate();

  // formでsubmitが実行された時に起動させる関数
  const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
    // submitのページ更新などの機能を中断させる
    e.preventDefault();

    // formのinputタグ・selectタグから、入力値・選択値を取得する
    const name: string = e.currentTarget.userName.value;
    const gender: string = e.currentTarget.userGender.value;
    const score: string = e.currentTarget.userScore.value;

    // createSearchParamsによってクエリパラメーターのオブジェクトを生成
    // toStroing()によって文字列化した後に、paramsに格納
    const params = createSearchParams({
      name: name,
      gender: gender,
      score: score
    }).toString();

    // navigateオブジェクトにpathを設定して、リダイレクト処理
    navigate(`/users/?${params}`, { replace: false });
  };

  return (
    <>
      <h1>ユーザー検索フォーム</h1>
      <form onSubmit={handleSubmit}>
        <label className="form__label">
          名前
          <input type="text" name="userName" defaultValue="" />
        </label>
        <label className="form__label">
          性別
          <select name="userGender" defaultValue="">
            <option value="">未選択</option>
            <option value="male">男性</option>
            <option value="female">女性</option>
          </select>
        </label>
        <label className="form__label">
          成績
          <select name="userScore" defaultValue="">
            <option value="">未選択</option>
            <option value="100">100点満点</option>
            <option value="90">90点以上</option>
            <option value="80">80点以上</option>
            <option value="70">70点以上</option>
            <option value="60">60点以上</option>
            <option value="50">50点以上</option>
            <option value="40">40点以上</option>
            <option value="30">30点以上</option>
            <option value="failure">30点未満</option>
          </select>
        </label>
        <button>検索する</button>
      </form>
    </>
  );
};
.form__label {
  display: block;
  margin-bottom: 20px;
}

.form__label > input {
  margin-left: 10px;
}

.form__label > select {
  margin-left: 10px;
}

最後に、 SearchResult.tsx ファイルです。

useSearchParams フックによって、 searchParams オブジェクトを生成しています。また、 searchParams オブジェクトの get メソッドを使うことで、クエリパラメーターから key に対応する値を取り出しています。

import React, { useState, useEffect } from "react";
// ユーザー情報をimport
import { users } from "../data/users";
// ユーザーオブジェクトの型をimport
import { User } from "../types/User";
// react-router-domライブラリから、useSearchPramsフックをimport
import { Link, useSearchParams } from "react-router-dom";

export const SearchResult: React.VFC = () => {
  // クエリパラメーターによって絞り込んだユーザー情報のstateを管理
  const [filteredUser, setFilteredUser] = useState<User[]>([]);

  // useEffectフックが起動したかどうかのstateを管理「
  const [flag, setFlag] = useState<boolean>(false);

  // useSearchPramsフックを使って、searchParamsオブジェクトを生成
  // 今回はsetterメソッドは不要なので、配列の第2要素を省略
  const [searchParams] = useSearchParams();

  // searchParamsオブジェクトのgetメソッドにkeyを渡して、対応する値を取得
  const name = searchParams.get("name");
  const gender = searchParams.get("gender");
  const score = searchParams.get("score");

  // クエリパラメーターとfilterメソッドを組み合わせて、条件に合うユーザー情報だけを抽出
  useEffect(
    () => {
      let copyUser = [...users];
      if (name) {
        copyUser = copyUser.filter((user) => user.name.match(name));
      }

      if (gender) {
        if (gender === "male")
          copyUser = copyUser.filter((user) => user.gender === "男性");
        else if (gender === "female") {
          copyUser = copyUser.filter((user) => user.gender === "女性");
        }
      }

      if (score) {
        if (score === "failure") {
          copyUser = copyUser.filter((user) => user.score < 30);
        } else {
          const numScore = Number(score);
          copyUser = copyUser.filter((user) => user.score >= numScore);
        }
      }

      setFlag(true);
      setFilteredUser(copyUser);
    },
    // ↓ 依存配列の空にしたことによるeslintの警告を抑制するための一文 ↓
    // eslint-disable-next-line react-hooks/exhaustive-deps
    []
  );

  return (
    <>
      <nav>
        <ul>
          <li>
            <Link to="/">TOP</Link>
          </li>
        </ul>
      </nav>
      <h1>検索でヒットしたユーザー</h1>
      {flag &&
        filteredUser.length > 0 &&
        filteredUser.map((user) => {
          return (
            <ul key={user.id}>
              <li>ID:{user.id}</li>
              <li>名前:{user.name}</li>
              <li>性別:{user.gender}</li>
              <li>成績:{user.score}点</li>
            </ul>
          );
        })}
      {flag && filteredUser.length === 0 && (
        <p>該当ユーザーが見つかりませんでした</p>
      )}
      {!flag && <p>該当ユーザーを検索中です……</p>}
    </>
  );
};

以上のファイルによって、このような検索フォームアプリが出来上がります。


この記事を知り合いに共有する
URLをコピーする
URLをコピーしました!
目次
閉じる