React Router (react-router-dom) v5のソースコードをv6へ書き換える

# はじめに

お疲れ様です、りつです！

現在Reactの学習を進めており、React Routerについて以下の動画で学習中です。

https://www.udemy.com/course/react_stepup/?couponCode=STEPUP20241001

非常にわかりやすい教材なのですが、React Routerのバージョンが5のため、現時点の最新バージョンである6を導入している環境だと一部エラーが発生する部分があります。

進める中でエラーが発生した部分や、バージョン6でどのように書き直せばよいのかについてまとめました。

なお、今回`StackBlitz`を使用しており、各バージョンは以下の状態で進めました。
- `react`: 18.3.1
- `react-dom`: 18.3.1
- `react-router-dom`: 6.27.0

※ 動画の内容をバージョン6の環境で進めており、エラーが発生した際に都度バージョン6で動くよう修正しているため、バージョン5のソースコードがない場合もあります。ご了承ください。

# 1. 基本的なページ遷移

バージョン6では`Switch`コンポーネントの代わりに`Routes`コンポーネントが用意されています。

https://reactrouter.com/en/main/upgrading/v5#upgrade-all-switch-elements-to-routes

> React Router v6 introduces a `Routes` component that is kind of like `Switch`, but a lot more powerful.

:::note warn
注意
バージョン5では`<Route exact>`という記法がありましたが、バージョン6では廃止されています。
（バージョン6では、特に何かを指定しなくてもパスが完全一致している場合に対象コンポーネントを表示してくれるようです）
:::

バージョン6で`Switch`コンポーネントを使用すると以下のようなエラーが発生します。
それぞれのバージョンのサンプルコードも合わせて記載しておりますのでご参照ください。

## エラー内容

```tex
Uncaught SyntaxError: The requested module '/node_modules/.vite/deps/react-router-dom.js?v=ef39f487' does not provide an export named 'Switch'
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3911933/3b378ebd-e808-c79f-535c-49962ac6e470.png)

## バージョン5の書き方

```react:App.jsx
import { BrowserRouter, Link, Switch, Route } from 'react-router-dom';

import { Home } from './Home';
import { Page1 } from './Page1';
import { Page2 } from './Page2';
import './styles.css';

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link>
        <br />
        <Link to="/page2">Page2</Link>
      </div>
      <Switch>
        <Route path="/">
          <Home />
        </Route>
        <Route path="page1">
          <Page1 />
        </Route>
        <Route path="page2">
          <Page2 />
        </Route>
      </Switch>
    </BrowserRouter>
  );
}
```

## バージョン6の書き方

```react:App.jsx
import { BrowserRouter, Link, Routes, Route } from 'react-router-dom';

import { Home } from './Home';
import { Page1 } from './Page1';
import { Page2 } from './Page2';
import './styles.css';

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link>
        <br />
        <Link to="/page2">Page2</Link>
      </div>
      <Routes>
        <Route path="/" element={<Home />}></Route>
        <Route path="/page1" element={<Page1 />}></Route>
        <Route path="/page2" element={<Page2 />}></Route>
      </Routes>
    </BrowserRouter>
  );
}
```

# 2. ネストされたページ遷移

遷移先のページで、さらにページ遷移用のリンクを用意する場合の書き方です。

## バージョン6の書き方

```react:App.jsx
import { BrowserRouter, Link, Routes, Route } from 'react-router-dom';

import { Home } from './Home';
import { Page1 } from './Page1';
import { Page2 } from './Page2';
import { Page1DetailA } from './Page1DetailA';
import { Page1DetailB } from './Page1DetailB';
import './styles.css';

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link>
        <br />
        <Link to="/page2">Page2</Link>
      </div>
      <Routes>
        <Route path="/" element={<Home />}></Route>
        <Route path="page1" element={<Page1 />}></Route>
        <Route path="page1/detailA" element={<Page1DetailA />}></Route>
        <Route path="page1/detailB" element={<Page1DetailB />}></Route>
        <Route path="page2" element={<Page2 />}></Route>
      </Routes>
    </BrowserRouter>
  );
}
```

```react:Page1.jsx
import { Link } from 'react-router-dom';

export const Page1 = () => {
  return (
    <div>
      <h1>Page1ページです</h1>
      <Link to="/page1/detailA">Page1DetailA</Link>
      <br />
      <Link to="/page1/detailB">Page1DetailB</Link>
    </div>
  );
};
```

## 結果

### `Page1`リンク押下時
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3911933/9e1afe48-7239-f064-12f5-8f4796c37078.png)

### `Page1DetailA`リンク押下時

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3911933/dc4fa576-f8be-96bc-7afb-cc98f07b3921.png)

# 3. 補足：ネストされたページ遷移（タブ切り替え風）

例えば、`Page1DetailA`のリンクを押下した際に、それまでのコンポーネントの表示は残したまま、`Page1DetailA`コンポーネントの内容を表示したい場合は`Outlet`を使用することで実現できます。

https://reactrouter.com/en/main/upgrading/v5#advantages-of-route-element

> When using a nested config, routes with `children` should render an `<Outlet>` in order to render their child routes. This makes it easy to render layouts with nested UI.

:::note
`Outlet`を使用する際のポイント
- `Route`コンポーネントを入れ子構造にする
- `Route`の`path`属性には、親のルートで指定したパス（今回の例では`page1`）以降のパスを指定する
:::

## バージョン6の書き方

```react:App.jsx
import { BrowserRouter, Link, Routes, Route } from 'react-router-dom';

import { Home } from './Home';
import { Page1 } from './Page1';
import { Page2 } from './Page2';
import { Page1DetailA } from './Page1DetailA';
import { Page1DetailB } from './Page1DetailB';
import './styles.css';

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link>
        <br />
        <Link to="/page2">Page2</Link>
      </div>
      <Routes>
        <Route path="/" element={<Home />}></Route>
        <Route path="page1" element={<Page1 />}>
          <Route path="detailA" element={<Page1DetailA />}></Route>
          <Route path="detailB" element={<Page1DetailB />}></Route>
        </Route>
        <Route path="page2" element={<Page2 />}></Route>
      </Routes>
    </BrowserRouter>
  );
}
```

```react:Page1.jsx
import { Link, Outlet } from 'react-router-dom';

export const Page1 = () => {
  return (
    <div>
      <h1>Page1ページです</h1>
      <Link to="/page1/detailA">Page1DetailA</Link>
      <br />
      <Link to="/page1/detailB">Page1DetailB</Link>

      <Outlet />
    </div>
  );
};
```

## 結果

### `Page1`リンク押下時

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3911933/385befb2-5be2-bab0-91c9-8a6a9e9e2338.png)

### `Page1DetailA`リンク押下時

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3911933/9eb78aa8-3871-b951-4343-64aed82f75a1.png)

# 4. URLパラメータ

URLパラメータを扱うには、`useParams`を利用します。
こちらはバージョン5と6で書き方に大きな違いはありません。

※ 以降のサンプルですが、ルート定義を`router/Router.jsx`へ分割していますのでご注意ください。

## バージョン6の書き方

```react:App.jsx
import { BrowserRouter, Link } from 'react-router-dom';

import './styles.css';
import { Router } from './router/Router';

export default function App() {
  return (
    <BrowserRouter>
      <div className="App">
        <Link to="/">Home</Link>
        <br />
        <Link to="/page1">Page1</Link>
        <br />
        <Link to="/page2">Page2</Link>
      </div>
      <Router />
    </BrowserRouter>
  );
}
```

```react:router/Router.jsx
import { Routes, Route } from 'react-router-dom';

import { Home } from '../Home';
import { Page1 } from '../Page1';
import { Page2 } from '../Page2';
import { Page1DetailA } from '../Page1DetailA';
import { Page1DetailB } from '../Page1DetailB';
import { UrlParameter } from '../UrlParameter';

export const Router = () => {
  return (
    <Routes>
      <Route path="/" element={<Home />}></Route>
      <Route path="page1" element={<Page1 />}></Route>
      <Route path="page1/detailA" element={<Page1DetailA />}></Route>
      <Route path="page1/detailB" element={<Page1DetailB />}></Route>
      <Route path="page2" element={<Page2 />}></Route>
      <Route path="page2/:id" element={<UrlParameter />}></Route>
    </Routes>
  );
};
```

```react:Page2.jsx
import { Link } from 'react-router-dom';

export const Page2 = () => {
  return (
    <div>
      <h1>Page2ページです</h1>
      <Link to="/page2/999">URL Parameter</Link>
    </div>
  );
};
```

```react:UrlParameter.jsx
import { useParams } from 'react-router-dom';

export const UrlParameter = () => {
  const { id } = useParams();
  return (
    <div>
      <h1>UrlParameterページです</h1>
      <p>パラメータは {id} です</p>
    </div>
  );
};
```

# 5. クエリパラメータ

クエリパラメータを扱うには、`useLocation`を利用します。
こちらもバージョン5と6で書き方に大きな違いはありません。

## バージョン6の書き方

```react:Page2.jsx
import { Link } from 'react-router-dom';

export const Page2 = () => {
  return (
    <div>
      <h1>Page2ページです</h1>
      <Link to="/page2/999">URL Parameter</Link>
      <br />
      <Link to="/page2/999?name=hogehoge">Query Parameter</Link>
    </div>
  );
};
```

```react:UrlParameter.jsx
import { useLocation, useParams } from 'react-router-dom';

export const UrlParameter = () => {
  const { id } = useParams();
  const { search } = useLocation();
  const query = new URLSearchParams(search);

  return (
    <div>
      <h1>UrlParameterページです</h1>
      <p>パラメータは {id} です</p>
      <p>クエリパラメータは {query.get('name')} です</p>
    </div>
  );
};
```

# 6. `state`の受け渡し

:::note warn
注意
stateで値を遷移先に受け渡す場合の`Link`部分の書き方が、バージョン5とバージョン6で異なるため注意してください。
- バージョン5：`to`属性にオブジェクトを指定し、`pathname`キーに遷移先のパス、`state`キーに受け渡したいstateの値をそれぞれ指定する
- バージョン6：`state`属性に受け渡したいstateの値を指定する
:::

https://reactrouter.com/en/main/upgrading/v5#pass-link-state-as-separate-prop

> The `Link` component in v6 accepts `state` as a separate prop instead of receiving it as part of the object passed to `to` so you'll need to update your `Link` components if they are using `state`:

> The state value is still retrieved in the linked component using `useLocation()`:

`state`の値を受け取る際は、バージョン5同様`useLocation()`を使用します。

## バージョン5の書き方

```react:Page1.jsx
import { Link } from 'react-router-dom';

export const Page1 = () => {
  const arr = [...Array(100).keys()];

  return (
    <div>
      <h1>Page1ページです</h1>
      <Link to={{ pathname: '/page1/detailA', state: arr }}>Page1DetailA</Link>　// to属性に指定
      <br />
      <Link to="/page1/detailB">Page1DetailB</Link>
    </div>
  );
};
```

## バージョン6の書き方

```react:Page1.jsx
import { Link } from 'react-router-dom';

export const Page1 = () => {
  const arr = [...Array(100).keys()];

  return (
    <div>
      <h1>Page1ページです</h1>
      <Link to="/page1/detailA" state={arr}>　// state属性に指定
        Page1DetailA
      </Link>
      <br />
      <Link to="/page1/detailB">Page1DetailB</Link>
    </div>
  );
};
```

## stateの受け取り（共通）

```react:Page1DetailA.jsx
import { useLocation } from 'react-router-dom';

export const Page1DetailA = () => {
  const { state } = useLocation();
  console.log(state);

  return (
    <div>
      <h1>Page1DetailAページです</h1>
    </div>
  );
};
```
# 7. `Link`を使わないページ遷移

:::note warn
注意
バージョン5では`useHistory`を使用していましたが、バージョン6では以下のエラーになります。
バージョン6では`useHistory`の代わりに`useNavigate`が用意されています。
使用方法が`useHistory`とは微妙に異なるのでご注意ください。
:::

https://reactrouter.com/en/main/upgrading/v5#use-usenavigate-instead-of-usehistory

> React Router v6 introduces a new navigation API that is synonymous with `<Link>` and provides better compatibility with suspense-enabled apps.

## エラー内容

```text
Uncaught SyntaxError: The requested module '/node_modules/.vite/deps/react-router-dom.js?v=ef39f487' does not provide an export named 'useHistory'
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3911933/94299f2e-1ef1-61d0-1d35-b276abc7a8b0.png)

## バージョン5の書き方

```react:Page1.jsx
import { Link, useHistory } from 'react-router-dom';

export const Page1 = () => {
  const arr = [...Array(100).keys()];
  const history = useHistory();

  const onClickDetailA = () => history.push('/page1/detailA');

  return (
    <div>
      <h1>Page1ページです</h1>
      <Link to="/page1/detailA" state={arr}>
        Page1DetailA
      </Link>
      <br />
      <Link to="/page1/detailB">Page1DetailB</Link>
      <br />
      <button onClick={onClickDetailA}>DetailA</button>
    </div>
  );
};
```

```react:Page1DetailA.jsx
import { useLocation, useHistory } from 'react-router-dom';

export const Page1DetailA = () => {
  const { state } = useLocation();
  const history = useHistory();
  console.log(state);

  const onClickBack = () => history.goBack();

  return (
    <div>
      <h1>Page1DetailAページです</h1>
      <button onClick={onClickBack}>戻る</button>
    </div>
  );
};
```

## バージョン6の書き方

```react:Page1.jsx
import { Link, useNavigate } from 'react-router-dom';

export const Page1 = () => {
  const arr = [...Array(100).keys()];
  const navigate = useNavigate();

  const onClickDetailA = () => navigate('/page1/detailA');

  return (
    <div>
      <h1>Page1ページです</h1>
      <Link to="/page1/detailA" state={arr}>
        Page1DetailA
      </Link>
      <br />
      <Link to="/page1/detailB">Page1DetailB</Link>
      <br />
      <button onClick={onClickDetailA}>DetailA</button>
    </div>
  );
};
```

```react:Page1DetailA.jsx
import { useLocation, useNavigate } from 'react-router-dom';

export const Page1DetailA = () => {
  const { state } = useLocation();
  const navigate = useNavigate();
  console.log(state);

  const onClickBack = () => navigate(-1); // ひとつ前の画面に戻る

  return (
    <div>
      <h1>Page1DetailAページです</h1>
      <button onClick={onClickBack}>戻る</button>
    </div>
  );
};
```

# おわりに

個人的には、バージョン6の方がバージョン5よりもすっきり書けるようになっているなと感じました。

なお、今回バージョン5のソースコードをバージョン6で動くように修正するにあたり、公式サイトが大変役立ちました。

英語ではありますが、サンプルコード付きで解説されていますので、ぜひ参考にしてみてください！


# 参考

https://reactrouter.com/en/main/upgrading/v5

https://www.udemy.com/course/react_stepup/?couponCode=STEPUP20241001