はじめに
お疲れ様です、りつです!
現在Reactの学習を進めており、React Routerについて以下の動画で学習中です。
非常にわかりやすい教材なのですが、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
コンポーネントが用意されています。
React Router v6 introduces a
Routes
component that is kind of likeSwitch
, but a lot more powerful.
注意
バージョン5では<Route exact>
という記法がありましたが、バージョン6では廃止されています。
(バージョン6では、特に何かを指定しなくてもパスが完全一致している場合に対象コンポーネントを表示してくれるようです)
バージョン6でSwitch
コンポーネントを使用すると以下のようなエラーが発生します。
それぞれのバージョンのサンプルコードも合わせて記載しておりますのでご参照ください。
エラー内容
Uncaught SyntaxError: The requested module '/node_modules/.vite/deps/react-router-dom.js?v=ef39f487' does not provide an export named 'Switch'
バージョン5の書き方
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の書き方
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の書き方
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>
);
}
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
リンク押下時
Page1DetailA
リンク押下時
3. 補足:ネストされたページ遷移(タブ切り替え風)
例えば、Page1DetailA
のリンクを押下した際に、それまでのコンポーネントの表示は残したまま、Page1DetailA
コンポーネントの内容を表示したい場合はOutlet
を使用することで実現できます。
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.
Outlet
を使用する際のポイント
-
Route
コンポーネントを入れ子構造にする -
Route
のpath
属性には、親のルートで指定したパス(今回の例ではpage1
)以降のパスを指定する
バージョン6の書き方
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>
);
}
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
リンク押下時
Page1DetailA
リンク押下時
4. URLパラメータ
URLパラメータを扱うには、useParams
を利用します。
こちらはバージョン5と6で書き方に大きな違いはありません。
※ 以降のサンプルですが、ルート定義をrouter/Router.jsx
へ分割していますのでご注意ください。
バージョン6の書き方
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>
);
}
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>
);
};
import { Link } from 'react-router-dom';
export const Page2 = () => {
return (
<div>
<h1>Page2ページです</h1>
<Link to="/page2/999">URL Parameter</Link>
</div>
);
};
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の書き方
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>
);
};
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
の受け渡し
注意
stateで値を遷移先に受け渡す場合のLink
部分の書き方が、バージョン5とバージョン6で異なるため注意してください。
- バージョン5:
to
属性にオブジェクトを指定し、pathname
キーに遷移先のパス、state
キーに受け渡したいstateの値をそれぞれ指定する - バージョン6:
state
属性に受け渡したいstateの値を指定する
The
Link
component in v6 acceptsstate
as a separate prop instead of receiving it as part of the object passed toto
so you'll need to update yourLink
components if they are usingstate
:
The state value is still retrieved in the linked component using
useLocation()
:
state
の値を受け取る際は、バージョン5同様useLocation()
を使用します。
バージョン5の書き方
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の書き方
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の受け取り(共通)
import { useLocation } from 'react-router-dom';
export const Page1DetailA = () => {
const { state } = useLocation();
console.log(state);
return (
<div>
<h1>Page1DetailAページです</h1>
</div>
);
};
7. Link
を使わないページ遷移
注意
バージョン5ではuseHistory
を使用していましたが、バージョン6では以下のエラーになります。
バージョン6ではuseHistory
の代わりにuseNavigate
が用意されています。
使用方法がuseHistory
とは微妙に異なるのでご注意ください。
React Router v6 introduces a new navigation API that is synonymous with
<Link>
and provides better compatibility with suspense-enabled apps.
エラー内容
Uncaught SyntaxError: The requested module '/node_modules/.vite/deps/react-router-dom.js?v=ef39f487' does not provide an export named 'useHistory'
バージョン5の書き方
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>
);
};
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の書き方
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>
);
};
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で動くように修正するにあたり、公式サイトが大変役立ちました。
英語ではありますが、サンプルコード付きで解説されていますので、ぜひ参考にしてみてください!
参考