React Routerは複数のページを持つReactアプリケーションを構築する際に利用されるライブラリです。複数のページが存在するということはブラウザからアクセスするためのURLが複数存在することになります。レイアウトなどの共通部分もありますがページ毎に異なるコンポーネントで構成されているためURLとコンポーネントを紐づける仕組みが必要となります。その役割を担うのがReact Routerです。React Routerを利用することで例えばブラウザからURLの/aboutにアクセスするとAboutコンポーネントの内容を表示、別のURLである/contactにアクセスするとContactコンポーネントの内容を表示させるといったことが可能になります。

また通常のHTMLでのページ間の移動ではページを移動する度にブラウザからサーバにリクエストを送り、サーバからHTMLを受け取りページ全体を描写するためページ全体のリロードが必要となります。React Routerを利用した場合はページ間の移動毎にページ全体のリロードを行うのではなくJavaScriptを使ってページ内の更新が必要な箇所のみ更新を行うためページ全体のリロードを行う必要がなくなりSPA(シングルページアプリケーション)としてスムーズにページ移動を行うことができます。

本文書ではReact Routerを初めて設定する人を対象にシンプルなコードを使ってReact Routerの基本について説明を行っています。一緒に手を動かすことでReact Routerの理解を深めることができます。

本文書はReact Router v5を利用して動作確認を行っていますReact Router v6については下記を参考にしてください。

プロジェクトの作成

React Routerの動作確認を行うためcreate-react-appコマンドを利用してReactプロジェクトの作成を行います。


 % npx create-react-app react-router-practise
create-react-appコマンドでなくviteを利用したnpm create vite@latestコマンドでプロジェクトを作成することもできます。
fukidashi

React Routerのインストール

Reactのプロジェクトのインストールが完了したらreact-router-domのインストールを行います。最新バージョンがv6のためreact-router-domをインストールを行うとv6がインストールされるのでバージョン指定を行う必要があります。


 % cd react-router-practise
 % npm install react-router-dom@v5

Reactの動作確認

react-router-domのインストールが完了したら、srcフォルダの中にあるApp.jsファイルを下記のように更新します。


function App() {
  return (
    <div>
      <h1>Hello React Router</h1>
    </div>
  );
}

export default App;

npm startコマンドでReactアプリケーションを起動します。


 % npx start
Compiled successfully!

You can now view react-router-practise in the browser.

  Local:            http://localhost:3000
  On Your Network:  http://192.168.2.184:3000

Note that the development build is not optimized.
To create a production build, use npm run build.

ブウラザからhttp://localhost:3000にアクセスしてHello React Routerが表示されるか確認します。

Reactの起動
Reactの起動

ルーティングの設定

これからルーティングの設定を行なっていきますがルーティングの設定を行う前にコンポーネントの作成を行います。http://localhost:3000/にアクセスした場合にはHomeコンポーネント、/aboutページにアクセスした場合にAboutコンポーネント、/contactページにアクセスしたい場合にContactコンポーネントが表示されるようにApp.jsにコンポーネントを追加します。


function App() {
  return (
    <div>
      <h1>Hello React Router</h1>
    </div>
  );
}

function Home() {
  return <h2>Home</h2>;
}

function About() {
  return <h2>About</h2>;
}

function Contact() {
  return <h2>Contact</h2>;
}

export default App;
ここではApp.jsファイルにHome, About, Contactコンポーネントを記述していますが通常はコンポーネント毎にファイルを独立させimportとして利用します。
fukidashi

はじめてのルーティング設定

追加したコンポーネントをルーティング機能を使って表示させるためにはreact-router-domからBrowserRouter, Routeコンポーネントをimportします。


import { BrowserRouter, Route } from 'react-router-dom';
function App() {
//略

importしたBrowserRouterコンポーネントで全体を包み、BrowerRouterコンポーネントの中ではRouteコンポーネントを使ってルーティングを定義していきます。Routeのpathに/(ルート)を指定することで、/(ルート)にアクセスするとHomeコンポーネントの内容が表示されます。


import { BrowserRouter, Route } from 'react-router-dom';
function App() {
  return (
    <BrowserRouter>
      <h1>Hello React Router</h1>
      <Route path="/">
        <Home />
      </Route>
    </BrowserRouter>
  );
}
BrowserRouterにはエイリアス(別名)としてRouterという名前を設定することもできます。その場合はBrowerRouter as Routerと記述します。エイリアスを利用した場合はタグの名前がBrowserRouterではなくRouterとなります。
fukidashi

ブラウザからlocalhost:3000/にアクセスするHomeコンポーネントの中で記述した文字列Homeが表示されることが確認できます。

最初のルーティング
最初のルーティング

Routeコンポーネントの子要素としてHomeコンポーネントを設定していますがcomponent propsを使って以下のように設定することも可能です。


import { BrowserRouter, Route } from 'react-router-dom';
function App() {
  return (
    <BrowserRouter>
      <h1>Hello React Router</h1>
      <Route path="/" component={Home} />
    </BrowserRouter>
  );
}
後ほどコンポーネントでのpropsの受け取りの中で説明を行いますが記述する方法によって動作が変わります。
fukidashi

Homeコンポーネントを表示することができたのでAboutコンポーネント, Contactページも追加してみましょう。


import { BrowserRouter, Route } from 'react-router-dom';
function App() {
  return (
    <BrowserRouter>
      <h1>Hello React Router</h1>
      <Route path="/">
        <Home />
      </Route>
      <Route path="/about">
        <About />
      </Route>
      <Route path="/contact">
        <Contact />
      </Route>
    </BrowserRouter>
  );
}

設定が完了したらブラウザからアクセスを行います。

/(ルート:http://localhost/)の場合は先ほどと同様にHomeのみ表示され先ほど確認した内容との違いはありません。

最初のルーティング
/(ルート)にアクセス

次に/about(http://localhost/about)にアクセスするとAboutコンポーネントの内容だけではなくHomeコンポーネントの中身も一緒に表示されます。

/aboutへのアクセス
/aboutへのアクセス

/contact(http://localhost:3000/contact)にアクセスするとContactコンポーネントの内容だけではなくHomeコンポーネントの中身も一緒に表示されます。

/contactへのアクセス
/contactへのアクセス

/about, /contactにアクセス時に表示されている内容から考えると/aboutにアクセスした時には”/”も”/about”に含まれているため”/”に対応しているHomeコンポーネントも表示されているのではと想像ができます。

exactの設定

この問題を解決するためにRouteコンポーネントにpropsのexactを設定します。


<BrowserRouter>
  <h1>Hello React Router</h1>
  <Route exact path="/">
    <Home />
  </Route>
  <Route path="/about">
    <About />
  </Route>
  <Route path="/contact">
    <Contact />
  </Route>
</BrowserRouter>

exactを設定後に再度/aboutにアクセスするとAboutコンポーネントの中身のみ表示されることが確認できます。exactを設定することでpathに設定した値がURLに完全に一致した場合のみ対応するコンポーネントが表示されることになります。

Routeへのexactの設定
Routeへのexactの設定
exactを設定するのはpathが”/”の場合のみで他のルーティングに設定する必要はありません。
fukidashi

ページがない404 Not Foundの設定

ここまでの設定では3つのURLにアクセスするために手動でURLを記述していました。もしaboutのスペルミスがありaboubと入力した場合や意図的にルーティングの設定のないページへのアクセスが行われた場合の動作を確認しておきます。

下記のようにルーティングに設定がないURLにアクセスがあるとaboutbに対応するコンポーネントが存在しないので”Hello React Router”以外の文字列は表示されません。

存在しないパスへのアクセス
存在しないパスへのアクセス

新たにApp.jsファイルにNotFoundコンポーネントを追加します。NotFoundは任意の名前なので好きな名前を付けてください。


function NotFound() {
  return <h2>Not Found Page</h2>;
}

コンポーネントを作成したら追加したNotFoundコンポーネントをルーティングに追加します。これまではRouteコンポーネントのpathにURLを設定していましががNot Foundコンポーネントの場合はpathの設定は行いません。


function App() {
  return (
    <BrowserRouter>
      <h1>Hello React Router</h1>
      <Route exact path="/">
        <Home />
      </Route>
      <Route path="/about">
        <About />
      </Route>
      <Route path="/contact">
        <Contact />
      </Route>
      <Route>
        <NotFound />
      </Route>
    </BrowserRouter>
  );
}

NotFoundのRouteコンポーネントを設定後、ルーティングに設定していないパスへのアクセスを行うとすべてNot Found Pageが表示されることが確認できます。

Not Foundコンポーネント表示
Not Foundコンポーネント表示

しかし、/(ルート), /about, /contactにアクセスするとNot Found Pageの文字列が必ず一緒に表示されます。

Not Foundコンポーネントが必ず表示
Not Foundコンポーネントが必ず表示

Switchコンポーネントの設定

/aboutであればAboutコンポーネントのみ/contactであればContactコンポーネントのみ表示といったようにpathがURLに一致するコンポーネントのみ表示させるためにSwichコンポーネントを追加します。

Switchコンポーネントの動作は複数の条件分岐に利用するswitch関数と同様の動作となるのでswitch関数をイメージすればSwitchコンポーネントを理解することができます。switch関数のdefaultがここでのNot Foundに対応します。
fukidashi

すべてのRouteコンポーネントをSwitchコンポーネントで包みます。またreact-router-domからSwitchコンポーネントをimportします。


import { BrowserRouter, Route, Switch } from 'react-router-dom';
function App() {
  return (
    <BrowserRouter>
      <h1>Hello React Router</h1>
      <Switch>
        <Route exact path="/">
          <Home />
        </Route>
        <Route path="/about">
          <About />
        </Route>
        <Route path="/contact">
          <Contact />
        </Route>
        <Route>
          <NotFound />
        </Route>
      </Switch>
    </BrowserRouter>
  );
}

Switchコンポーネント追加後に/contactにアクセスするとContactコンポーネントの内容のみ表示できるようになります。

Switchコンポーネント追加後の動作
Switchコンポーネント追加後の動作

Routeコンポーネントのpathに設定したURL以外にアクセスがあった場合はNot Foundコンポーネントが表示され、pathに設定されているURLにアクセスがあった場合は対応するコンポーネントの内容が表示させることが確認できます。

リンクの設定

ここまでの設定では各ページにアクセスするためには手動でURLを入力する必要があります。ここではリンクを追加しリンクをクリックするとページの移動ができるように設定を行なっていきます。


function App() {
  return (
    <BrowserRouter>
      <h1>Hello React Router</h1>
      <ul>
        <li>
          <a href="/">Home</a>
        </li>
        <li>
          <a href="/about">About</a>
        </li>
        <li>
          <a href="/contact">Contact</a>
        </li>
      </ul>
      <Switch>
        <Route exact path="/">
          <Home />
        </Route>
        <Route path="/about">
          <About />
        </Route>
        <Route path="/contact">
          <Contact />
        </Route>
        <Route>
          <NotFound />
        </Route>
      </Switch>
    </BrowserRouter>
  );
}

ブラウザで確認すると設定通り各ページへのリンクが表示されます。リンクボタンをクリックするとクリックしたページが表示されますが表示される際にページ全体がリロードされます。表示される内容が少ないこともありリロードがあっという間に終わってしまうためリロードが行われているかどうか少しわかりにくい場合もありますが次に行う設定と比較することで動作の違いが実感できると思います。

リンクの設定
リンクの設定

Linkコンポーネントの設定

クリックした際にページ全体をリロードするaタグとは異なりページ内の更新が必要な箇所のみ更新を行えるようにLinkコンポーネントを設定します。Linkコンポーネントはreact-router-domからimportする必要があります。aがLinkになり、hrefがtoに変更しています。


import { BrowserRouter, Route, Switch, Link } from 'react-router-dom';
function App() {
  return (
    <BrowserRouter>
      <h1>Hello React Router</h1>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
        <li>
          <Link to="/contact">Contact</Link>
        </li>
      </ul>
      //略

ブラウザで確認すると表示されているページの内容に変化はありませんがリンクをクリックするとページのリロードが行われないのでページの移動がスムーズに行われることがわかるかと思います。

NavLinkコンポーネントの設定

普段利用するWEBアプリケーションでは現在どのページにアクセスしているのかわかるようにメニューの背景色、リンクのテキスト文字の色が変わったりと装飾が行われているものがほとんどかと思います。

NavLinkコンポーネントを利用することで現在アクセスしているページがどこなのか装飾することが可能になります。NavLinkコンポーネントではactiveNameClass propsを設定することができ設定したNavLinkに設定したURLにアクセスが行われるとactiveNameClassに設定したclassが動的にリンクに追加されます。classにカラーに関するスタイルを設定しておくことで現在自分がどこのページを閲覧しているかリンクから判断することができます。

説明よりも実際に設定を行うとすぐに理解することができるので動作確認を行います。まずreact-router-domからのimportも含めLinkからNavLinkに変更して、activeNameClass propsを追加し設定値をactiveとします。これがアクセス時にリンクタグに追加されるclassです。


import { BrowserRouter, Route, Switch, NavLink } from 'react-router-dom';
function App() {
  return (
    <BrowserRouter>
      <h1>Hello React Router</h1>
      <ul>
        <li>
          <NavLink activeClassName="active" to="/">
            Home
          </NavLink>
        </li>
        <li>
          <NavLink activeClassName="active" to="/about">
            About
          </NavLink>
        </li>
        <li>
          <NavLink activeClassName="active" to="/contact">
            Contact
          </NavLink>
        </li>
      </ul>
//略
  );
}

classが追加されただけでは何も変化はないのでactiveクラスの設定も必要になります。ここまでの設定でインストール時からindex.jsファイルを更新していなければindex.jsファイルでindex.cssファイルがimportされているのでindex.cssファイルにactiveクラスを追加します。ここは簡易的にテキストカラーをblueにする設定を行います。


.active {
  color: blue;
}

/contactページにアクセスするとactiveクラスが適用されているのがわかりますがHomeとContactの2つの色が変わっています。

NavLinkの設定でリンクの色がブルーに
NavLinkの設定でリンクの色がブルーに

/aboutにアクセスしてもHomeとAboutの文字の色が変わりますがHomeにアクセスするとHomeのみの色が変わります。似たような現象がRouteコンポーネントの設定時もあったと思います。その場合はRouteタグにexactを設定することで問題を解消しました。NavLinkの場合も同じで/contactとアクセスすると”/”が含まれているのでルートのリンクであるHomeの色も変わります。この問題を回避するためここでも/(ルート)のNavLinkにexactを設定します。


<ul>
  <li>
    <NavLink activeClassName="active" exact to="/">
      Home
    </NavLink>
  </li>

設定後に/contactにアクセスするとContactのリンクのみ色がわかっていることが確認できます。これで今自分がどのページにアクセスしているかがリンクの色を見て判断できるようになりました。

NavLinkにexactを設定
NavLinkにexactを設定

NavLinkに直接styleを適用したい場合はactiveStyle propsを使うことができます。


<ul>
  <li>
    <NavLink
      activeStyle={{
        fontWeight: 'bold',
        color: 'red',
      }}
      exact
      to="/"
    >
      Home
    </NavLink>
  </li>

/(ルート)のみにactiveStyleを設定したので/(ルート)にアクセスすると太文字で赤色の文字になります。リンク毎に別の色を設定することも可能です。

NavLinkへのactiveStyle設定
NavLinkへのactiveStyle設定

Dynamic Routeの設定

propsを取得

これまでの設定では/aboutにアクセスするとAboutコンポーネント, /contactにアクセスするとContactコンポーネントが表示されURLとコンポーネントが1対1の対応でした。コーポレートサイトなどのページ数が少ない場合はこれで問題はありませんがブログサイトのように/postsの下に/posts/1,/posts/2…, /posts/100にページが存在する場合にそれぞれのURLに対応するコンポーネントを作成することは現実的ではありません。そのような場合にDynamic Routeを利用します。/posts/1にアクセスがあると1というURLにアクセスがあったことを識別しその1の情報を元に動的にページの内容を変えていきます。Dymamic Routeによって複数のURLに1つのコンポーネントが紐づけられるという設定が可能になります。

Dynamic Routeの設定を行うために新たにPostsコンポーネントとルーティングの/postsの追加を行います。


<li>
  <NavLink activeClassName="active" to="/posts">
    Posts
  </NavLink>
</li>
//略
<Route path="/posts">
  <Posts />
</Route>
//略
function Posts() {
  return <h2>Post List</h2>;
};

Postsコンポーネントにはルーティングに関する情報をpropsを通して取得することができるのですがこれまでの上記の記述方法ではpropsが取得できないので以下のようにcomponent propsにコンポーネントを指定します。コンポーネントが受け取ったpropsにはどのような情報が含まれているのか確認するためにconsole.logを使ってブラウザのコンソールに表示させます。


<li>
  <NavLink activeClassName="active" to="/posts">
    Posts
  </NavLink>
</li>
//略
<Route path="/posts" component={Posts} />
//略
function Posts(props) {
  console.log(props);
  return <h2>Post List</h2>;
};

設定後にpostsのリンクをクリックするとコンソールログに以下の内容が表示されます。

propsの内容を表示
propsの内容を表示

propsの中にはhistory, location, matchオブジェクトが含まれていることがわかります。locationやmatchにはアクセスのあったURLのパス情報が含まれていることがわかります。

Postコンポーネントの追加

/posts/1, /posts/2というようにIDが異なるURLにアクセスがあった場合にも一つのPostコンポーネントで対応できるように新たにルーティング/posts/:idを追加します。”:(コロン)”が必須となります。


<Route exact path="/posts" component={Posts} />
<Route path="/posts/:id" component={Post} />

// or 

<Route path="/posts/:id" component={Post} />
<Route path="/posts" component={Posts} />
コンポーネントの並び順がPosts, Postの場合は/posts/1にアクセスした場合も/postsのPostsコンポーネントが表示されるのでexactを設定してください。exactを設定しない場合は並び順を逆にしてください。上から順番にチェックを行っているので/posts/1にアクセスがあった場合に先に/postsに一致するのでPostsコンポーネントの内容が表示されます。
fukidashi

/posts/:idの設定により/posts/1, /posts/2でもアクセスが可能になりますがアクセスしたURL毎に異なる内容を表示させるためには識別子としてURLに指定したidを取得する方法が必要になります。ここまでの方法ではidを取得する方法はありません。

idを取得するために必要となるのが先ほど/postsにアクセスした場合に取得したpropsです。propsにはURLが含まれていたことを思い出してください。

Postコンポーネントの追加を行いますが:idの値はpropsのmatch.params.idから取得することができます。取得した値をブラウザ上に表示できるように設定を行います。


function Post(props) {
  const id = props.match.params.id;
  return <h2>Post {id}</h2>;
}

手動でURLに/posts/1と入力すると下記のようにIDを表示することができます。

URLからIDを取得し表示
URLからIDを取得し表示

1と数字を入力しましたが文字列”about_dynamic_route”を入れてもブラウザ上に表示させることができます。これでURLに含まれる値がどのようなものでも取得できることがわかりました。

文字列をブラウザに表示
文字列をブラウザに表示

リンクの設定

手動で/posts/1と入力するのではなくリンクからアクセスできるようにposts配列を追加します。配列には要素を一意に識別できるidを持った複数のpost情報が含まれています。


const posts = [
  { id: 1, title: 'React', content: 'React Tutorial' },
  { id: 2, title: 'Vue', content: 'Vue.js Tutorial' },
  { id: 3, title: 'Laravel', content: 'Laravel Tutorail' },
];

Postsコンポーネントの中でmap関数を使ってpostsを展開します。idの値を使ううことでリンク先のURLが変わります。


const Posts = (props) => {
  return (
    <div>
      <h2>Post List</h2>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <NavLink to={`/posts/${post.id}`}>{post.title}</NavLink>
          </li>
        ))}
      </ul>
    </div>
  );
};

プラウブで確認すると/postsにアクセスするとリンクを持ったpostsの一覧が表示されます。

postsの情報を表示
postsの情報を表示

Postコンポーネントではpropsで受け取ったidを利用してposts配列の中から同じidを持つオブジェクトを見つけて表示させます。props.match.params.idの値がStringとして取得されるのでNumber関数を利用して数字にしています。またURLに含まれるidを持つpostを取得するためにfindメソッドを利用しています。


function Post(props) {
  const id = Number(props.match.params.id);
  const post = posts.find((post) => post.id === id);
  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.content}</p>
    </div>
  );
}

表示されているpostsのリストのReactをクリックするとURLに含まれるidと一致するidを持つpostが表示されます。リンク毎に異なる内容が表示されることを確認してください。このようにURLの値によって表示内容が変わるDynamic Routeの設定を行うことができました。

idによって表示される内容が変わる
idによって表示される内容が変わる

Postsコンポーネント内でのルーティング設定

ここまでではすべてのルーティング情報はAppコンポーネントの中に含まれていましたがPostコンポーネントに関するルーティングをPostsコンポーネント内で定義することも可能です。

Appコンポーネントに記述したPostのルーティングを削除してPostsコンポーネントに移動します。


function Posts(props) {
  return (
    <div>
      <h2>Post List</h2>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <NavLink to={`/posts/${post.id}`}>{post.title}</NavLink>
          </li>
        ))}
      </ul>
      <Route path="/posts/:id" component={Post} />
    </div>
  );
}

上記のようにPostコンポーネントのリンクをPostsコンポーネントに移動した場合は先ほどPostsコンポーネントのRouteに設定したexactを削除する必要があります。exactを残したままではPostコンポーネントのルーティングが追加されません。


<Route path="/posts" component={Posts} /> //exactを削除
{/* <Route path="/posts/:id" component={Post} /> */} //削除

useParamsフックによるidの取得

propsを利用してidの値を取得していましたがpropsを取得しなくてもuseParamsフックを使ってidを取得することが可能です。useParamsを利用するためにはreact-router-domからimportする必要があります。


import {
  BrowserRouter,
  Route,
  Switch,
  NavLink,
  useParams,
} from 'react-router-dom';
//略
function Post() {
  // const id = Number(props.match.params.id);
  const { id } = useParams();
  const post = posts.find((post) => post.id === Number(id));
  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.content}</p>
    </div>
  );
}

上記のコードでuseParams()の中身を確認すると下記のようなオブジェクトであることがわかります。idは/posts/:idで設定した:idです。/posts/:postIdとするとpostIdとして取得できます。


{id: "2"}
id: "2"

useParamsフックを利用してもpropsと別の方法でidを取得しているだけなので表示される内容は変わりません。

propsをPostコンポーネント内で取得する際にRouteコンポーネントのcomponent propsを利用するように変更を行いましたがuserParamsを利用する場合はcomponent propsを利用せず下記のような記述でもidを取得することができます。


<Route exact path="/posts">
  <Posts />
</Route>

//propsを利用する場合は
<Route exact path="/posts" component={Posts} />

useRouteMatchフックの利用

userParamsで先ほどはpropsを利用せずidを取得することができましたが、useRouteMatchフックを利用するとアクセスしているURLの情報を取得することができます。取得した値を利用してRouteコンポーネントの設定に利用することも可能です。


function Posts() {
  const { path, url } = useRouteMatch();
  return (
    <div>
      <h2>Post List</h2>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <Link to={`${url}/${post.id}`}>{post.title}</Link>
          </li>
        ))}
      </ul>
      <Route path={`${path}/:id`}>
        <Post />
      </Route>
    </div>
  );
};
propsの場合ではuserRouteMatchで取得したurlはprops.match.url, pathはprops.match.pathで取得することができます。
fukidashi

下記はpropsに含まれる情報です。

propsの内容を表示
propsの内容を表示

userRouteMatchフックの中身は下記のようになっています。

useHistory, useLocationフック

コンポーネントにが取得できるpropsの中にはhistory, location, matchのオブジェクトが入っていることを確認し、useRouteMatchフックを使うことでmatchの内容を取得できることがわかりました。残りのhistory, locatinについてもそれぞれuseHistory, useLocationフックを利用して取得することができます。Postコンポーネント内でconsole.logを利用して確認してみましょう。useHistory, useLocationフックも利用するためにはimportが必要です。


import {
  BrowserRouter,
  Route,
  Switch,
  NavLink,
  Link,
  useParams,
  useRouteMatch,
  useLocation,
  useHistory,
} from 'react-router-dom';
//略

function Posts() {
  const { path, url } = useRouteMatch();
  console.log(useRouteMatch());
  console.log(useLocation());
  console.log(useHistory());
  return (
    <div>
      <h2>Post List</h2>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <Link to={`${url}/${post.id}`}>{post.title}</Link>
          </li>
        ))}
      </ul>
      <Route path={`${path}/:id`}>
        <Post />
      </Route>
    </div>
  );
}

ブラウザのコンソールを確認するとpropsで取得したhistory, location, matchの中身で同じであることが確認できます。

Hookを使って情報を取得
Hookを使って情報を取得

useLocationフックの設定

useLocationフックから取得したオブジェクトにはpathname, search, hash, state, keyプロパティがあります。それらには値が入っていませんがいつ設定するのでしょう。

React RouterのドキュメントのLinkコンポーネントを確認するとtoにオブジェクトとして設定できることがわかります。

Linkコンポーネント
Linkコンポーネント

これまではtoに文字列でURLを入力していただけでしたが、オブジェクトとして先ほどuserLocationフックで表示されたpathname, search, hash, stateが設定できることがわかります。

例えばsearchはqueryパラメータの情報が保存されるとドキュメントに記述されているのでLinkのtoでオブジェクトを利用しなくてもURLにパラメータをつけることでuserLocationのsearchの値にsort=ascが保存されます。


<Link to={`${url}/${post.id}?sort=asc`}>{post.title}</Link>

コンソールで確認するとuseLocationフックの中にsort=ascが含まれていることがわかります。


{pathname: "/posts/1", search: "?sort=asc", hash: "", state: undefined, key: "vdlraw"}
hash: ""
key: "vdlraw"
pathname: "/posts/1"
search: "?sort=asc"
state: undefined

toにオブジェクトを設定しても同じ結果になることを確認しておきましょう。


<Link
  to={{
    pathname: `${url}/${post.id}`,
    search: '?sort=desc',
  }}
>
  {post.title}
</Link>

toにオブジェクを設定してもuseLocationのsearchプロパティに値が入っていることが確認できます。


{pathname: "/posts/1", search: "?sort=desc", hash: "", key: "47u01n"}
hash: ""
key: "47u01n"
pathname: "/posts/1"
search: "?sort=desc"
__proto__: Object

検索やソートのようにURLのパラメータを利用したい場合はuseLocationのsearchプロパティを利用できることがわかります。

パラメータの値を取得したい場合はURLSearchParamsクラスを利用することで簡単に値を取得することができます。


const query = new URLSearchParams(useLocation().search);
console.log(query.get('sort'));
//コンソール
desc

Redirectの設定

認証機能が実装されている場合ログインが完了していなければアクセス制限されたURLにアクセスしようとすると強制的にログインページにリダイレクトされます。そのような場合はRedirectコンポーネントを利用して強制的にリダイレクトさせることができます。

簡単なコードを使ってRedirectの動作を確認します。

変数のauthenticatedを定義しtrue, falseの真偽値を設定することができます。/postsにアクセス制限をかけauthenticatedがtrueの場合にはPostsコンポーネントが表示され、authenticatedの場合はRedirectコンポーネントのtoに設定されたURLにリダイレクトされます。


import {
  BrowserRouter,
  Route,
  Switch,
  NavLink,
  Link,
  useParams,
  useRouteMatch,
  Redirect,
} from 'react-router-dom';
function App() {
  const authenticated = false;
  //略
        <Route path="/posts">
          {authenticated ? <Posts /> : <Redirect to="/" />}
        </Route>
  //略

authenticatedがtrueの場合はPostsコンポーネントの内容が表示され、falseに設定した場合は/postsを含め、/posts/1, … /posts/3にアクセスしてもリダイレクトされHomeコンポーネントの内容が表示されます。

React Router v6については下記を参考にしてください。