[React] useLocation についての注意点
概要
React 向けのルーティング用ライブラリ React Router に用意されているフックの一つ、useLocation
を使用する上でいくつか躓いた点があったので、諸注意としてまとめた記事になります。
環境
ライブラリ | バージョン |
---|---|
create-react-app | 5.0.1 |
react | 19.1.0 |
react-dom | 19.1.0 |
react-router | 7.6.2 |
typescript | 4.9.5 |
なお、以下のコマンドで TypeScript を用いた React アプリを作成した場合について記載します。
npx create-react-app@latest my-app --template typescript
useLocation とは
useLocation
の公式 API Reference はこちらです。
Returns the current Location.
useLocation()
は現在の Location
を返します。
Location
の公式 API Reference は以下になります。
An entry in a history stack. A location contains information about the URL path, as well as possibly some arbitrary state and a key.
基本的にはブラウザのページ履歴の一つ一つが Location
オブジェクトに当たり、useLocation()
はその内最新の(現在の)ものを返します。
こうして返された最新 Location
オブジェクトを通じて、現在の URL のパス部分やクエリー文字列、任意の state を取得することができます。
<Router>
component.」の対処法
注意点 1. エラー「useLocation() may be used only in the context of a npm install react-router
以上のコマンドで React Router をインストールし、現在の URL などを取得するために useLocation
を利用しようとしてみます。
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
import "./App.css";
import { useLocation } from "react-router";
function App() {
const location = useLocation();
return <div>location.pathname: {location.pathname}</div>;
}
export default App;
すると以下のようなエラーが表示されます。
useLocation() may be used only in the context of a
<Router>
component.
つまり useLocation()
は<Router>
コンポーネントの子孫要素でのみ使用可能であり、React Router によるルーティングが必要となります。
ここで注意すべき点として、エラーメッセージには<Router>
component とありますが、<Router>
の API Reference には以下のような注意点が記載されています。
Note: You usually won't render a
<Router>
directly. Instead, you'll render a router that is more specific to your environment such as a<BrowserRouter>
in web browsers or a<StaticRouter>
for server rendering.
すなわち実際に使用すべきコンポーネントは Web ブラウザでは<BrowserRouter>
、サーバーレンダリングでは<StaticRouter>
となります。
今回はフロントエンドのみの実装なので<BrowserRouter>
を使用し、index.tsx
を以下のように書き換えます。
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
+ import { BrowserRouter, Routes, Route } from "react-router";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
- <App />
+ <BrowserRouter>
+ <Routes>
+ <Route path="/app" element={<App />} />
+ </Routes>
+ </BrowserRouter>
</React.StrictMode>
);
この修正によって<App>
内で呼び出している useLocation()
は<BrowserRouter>
の子孫要素となるためエラーが解消されます。
このようにuseLocation
を使用するためには React Router によるルーティングが必須となるため、今回の例のように URL のパス部分を取得したいだけの場合はライブラリを使用せずに単にwindows.location.pathname
で取得した方が良い場合もあります。
import "./App.css";
- import { useLocation } from "react-router";
function App() {
- const location = useLocation();
- return <div>location.pathname: {location.pathname}</div>;
+ return <div>location.pathname: {window.location.pathname}</div>;
}
export default App;
注意点 2. useLocation()で取得できる state は window.history で取得できる state と異なる
useLocation()
の返り値であるLocation
オブジェクトには state というプロパティが存在します。
A value of arbitrary data associated with this location.
一方で特別なライブラリを使用せずwindow.history
で呼び出せる History オブジェクトも state というプロパティを持っています。
state は History インターフェイスの読み取り専用プロパティで、履歴スタックの一番上の状態を表す値を返します。
名称は共にstate
で履歴スタックの最新の状態を表す値であり任意の key 名を使用できるところも同じですが、この 2 つは違う値を指します。
例として、ボタンによって page1 と page2 を交互に遷移するアプリを実装します。
import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import { BrowserRouter, Route, Routes } from "react-router";
import Page1 from "./pages/Page1";
import Page2 from "./pages/Page2";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
);
root.render(
<React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/page1" element={<Page1 />} />
<Route path="/page2" element={<Page2 />} />
</Routes>
</BrowserRouter>
</React.StrictMode>
);
import { useLocation, useNavigate } from "react-router";
function Page1() {
const location = useLocation();
const navigate = useNavigate();
return (
<>
<h1>page1</h1>
<div>
<h2>window</h2>
<div>window.location.pathname:{window.location.pathname}</div>
<div>window.history.state.stateA: {window.history.state?.stateA}</div>
<div>
<button
onClick={() => {
window.history.pushState(
{ stateA: "pushedState2" },
"",
"/page2"
);
window.location.reload();
}}
>
to page2 (window.history.pushState())
</button>
</div>
</div>
<div>
<h2>useLocation</h2>
<div>useLocation pathname: {location.pathname}</div>
<div>useLocation state.stateA: {location.state?.stateA}</div>
</div>
<div>
<h2>useHistory</h2>
<div>
<button
onClick={() =>
navigate("/page2", {
state: { stateA: "navigatedState2" },
})
}
>
to page2 (useNavigate navigate)
</button>
</div>
</div>
</>
);
}
export default Page1;
import { useLocation, useNavigate } from "react-router";
function Page2() {
const location = useLocation();
const navigate = useNavigate();
return (
<>
<h1>page2</h1>
<div>
<h2>window</h2>
<div>window.location.pathname:{window.location.pathname}</div>
<div>window.history.state.stateA: {window.history.state?.stateA}</div>
<div>
<button
onClick={() => {
window.history.pushState(
{ stateA: "pushedState1" },
"",
"/page1"
);
window.location.reload();
}}
>
to page1 (window.history.pushState())
</button>
</div>
</div>
<div>
<h2>useLocation</h2>
<div>useLocation pathname: {location.pathname}</div>
<div>useLocation state.stateA: {location.state?.stateA}</div>
</div>
<div>
<h2>useHistory</h2>
<div>
<button
onClick={() =>
navigate("/page1", {
state: { stateA: "navigatedState1" },
})
}
>
to page1 (useNavigate navigate)
</button>
</div>
</div>
</>
);
}
export default Page2;
上の動画のようにwindow.history.pushState()
+ window.location.reload()
によって最新の履歴スタックに任意の state を設定し画面更新を行った時、設定した state は
-
window.history.state
により取得することができる。(window.history.state.stateA: の値が更新される) -
useLocation()
の返り値の state で取得することはできない。(useLocation state.stateA: の値は更新されない)
となります。
一方で上の動画のようにuseNavigate()
の返す遷移用の関数(例では navigate)を使用して state の設定と画面遷移を行った時、設定した state は
-
window.history.state
により取得することはできない。(window.history.state.stateA: の値は更新されない) -
useLocation()
の返り値の state で取得することができる。(useLocation state.stateA: の値が更新される)
となります。
つまりuseLocation
の返り値の state はあくまでuseNavigate
などと一緒にReact Router の文脈で使用できる stateであり、window.history.pushState()
で設定しwindow.history.state
で取得できる state とは別のものということになります。
ここで少し紛らわしい要素として、window.history.pushState()
+ window.location.reload()
を使用してページ遷移した際もuseNavigate()
の返す遷移用の関数を使用してページ遷移した際も、ともに URL の変更自体は起こるため、
-
window.location.pathname
で取得できる URL のパス部分 -
useLocation
の返り値のpathname
で取得できる URL のパス部分
は同じ値を返します。(動画のwindow.location.pathname:
とuseLocation pathname:
の値)
あくまでwindow
を使用した場合とuseLocation
を使用した場合のstate が異なる値であるというのが今回の注意点になります。
Discussion