ページ遷移時に遷移先のページに何らかの情報を渡したい場面があるかと思います。
例えば商品一覧から商品詳細画面に移動するときは、商品のidを渡したいかもしれません。検索フォームに入力した値を、検索結果表示画面でも使いたくなるかもしれません。
このような時にreact-router-domが1つの選択肢としていいなと思ったので、アウトプットがてら紹介します。
ケース
例として、以下のようなケースを考えます。
- 計算画面(Cal.tsx)では、フォームに入力された値をもとに計算を行います
- ユーザーは、計算された値を確認し、この値を使って登録画面(Registration.tsx)から、データの登録を行うことができます
import { useState } from "react"
import { useNavigate } from "react-router-dom"
export default function Cal() {
const navigate = useNavigate()
const [unitPrice, setUnitPrice] = useState("")
const [quantity, setQuantity] = useState("1")
const u = Number(unitPrice)
const q = Number(quantity)
const subtotal = Number.isFinite(u) && Number.isFinite(q) ? u * q : NaN
const onClick = () => {
// 遷移先のページにsubtotalを渡したい!
navigate("/registration")
}
return (
<div>
<h1>計算</h1>
<input value={unitPrice} onChange={(e) => setUnitPrice(e.target.value)} placeholder="単価" />
<input value={quantity} onChange={(e) => setQuantity(e.target.value)} placeholder="数量" />
<p>合計: {Number.isFinite(subtotal) ? subtotal : "-"}</p>
<button onClick={onClick} disabled={!Number.isFinite(subtotal)}>
登録へ
</button>
</div>
)
}
export default function Registration() {
// subtotalはCal.tsxで計算した値を入れたい!
const subtotal = 0
return (
<div>
<h1>登録</h1>
<p>受け取った subtotal: {subtotal}</p>
{/* ここで subtotal を使って登録処理(API呼び出し等) */}
<button onClick={() => alert(`登録しました(subtotal=${subtotal})`)}>
登録する
</button>
</div>
)
}
方法1:URLに含める
やり方の1つはパスパラメータやクエリパラメータとして渡すという方法です。
const onClick = () => {
navigate(`/registration?subtotal=${subtotal.toString()}`)
}
export default function Registration() {
const [searchParams] = useSearchParams()
const raw = searchParams.get("subtotal") // string | null
const subtotal = raw ? Number(raw) : NaN
このようにすれば値を渡すことができます。
ただ、渡したい値が増えると可読性などの問題がありますし、URLに含めるにはセキュリティ等の観点から難しい値(例:機微情報など)はこの方法を使うことができません。
方法2:グローバルな状態を使う
useContextやReduxなど、どこからでもアクセスできる状態(state)に一度格納し、Registration.tsxでこれを取得する方法です。
(実装は面倒なので省略)
渡したい値が増えても対応できますが、正直実装が面倒です。グローバルな状態はどのコンポーネントからもアクセスできてしまうので、この点でもあまり好ましくない結果になることもあります。
方法3:useLocation()を使う
本題です。
react-router-dom には useLocation() が用意されており、これを使うことで、もっと簡単に遷移先のページに値を渡すことができます。
リロードすると値はリセットされる等のデメリットはありますが、一時的に、簡単な実装で渡すことができます!
import { useState } from "react"
import { useNavigate } from "react-router-dom"
export default function Cal() {
const navigate = useNavigate()
const [unitPrice, setUnitPrice] = useState("")
const [quantity, setQuantity] = useState("1")
const u = Number(unitPrice)
const q = Number(quantity)
const subtotal = Number.isFinite(u) && Number.isFinite(q) ? u * q : NaN
const onClick = () => {
if (!Number.isFinite(subtotal)) return
// useLocationで取得できるように、値をセットする!
navigate("/registration", { state: { subtotal } })
}
return (
<div>
<h1>計算</h1>
<input value={unitPrice} onChange={(e) => setUnitPrice(e.target.value)} placeholder="単価" />
<input value={quantity} onChange={(e) => setQuantity(e.target.value)} placeholder="数量" />
<p>合計: {Number.isFinite(subtotal) ? subtotal : "-"}</p>
<button onClick={onClick} disabled={!Number.isFinite(subtotal)}>
登録へ
</button>
</div>
)
}
import { useLocation } from "react-router-dom"
export default function Registration() {
// useLocationを使ってsubtotalを取得!
const location = useLocation()
const subtotal = (location.state as { subtotal?: number } | null)?.subtotal
if (typeof subtotal !== "number") {
return <p>計算結果がありません。Cal画面から遷移してください。</p>
}
return (
<div>
<h1>登録</h1>
<p>受け取った subtotal: {subtotal}</p>
{/* ここで subtotal を使って登録処理(API呼び出し等) */}
<button onClick={() => alert(`登録しました(subtotal=${subtotal})`)}>
登録する
</button>
</div>
)
}
まとめ
| 方式 | メリット | デメリット | 利用に適したシーン |
|---|---|---|---|
| URLに含める(クエリ/パス) | リロードしても保持される/URL共有で同じ状態を再現できる/デバッグしやすい(値が見える)/SSRや外部リンクと相性が良い | ユーザーが改ざんできる(信用不可)/機微情報を載せられない/複雑なオブジェクトを扱いにくい(シリアライズ・長さ制限) | 検索条件・フィルタ・ページング・ソート・タブ選択など「画面状態をURLで再現したい」ケース/サポート用にURL共有したいケース |
| グローバルstate(Redux/Zustand等) | どこからでも参照可能/大きいデータ・複雑なオブジェクトを渡しやすい/多画面にまたがる状態管理が得意 | リロードで消える(永続化しない限り)/依存が増えて追跡しづらい(どこでセットされたか見えにくい)/導入・設計コストが上がる(永続化するとさらに) | ログインユーザー・権限・UI設定・カートなど「アプリ全体で使う状態」/ステップフォーム・ウィザードなど「複数画面で編集しながら最後に送る」 |
useLocation(location state)(navigate(...,{ state })) |
URLが汚れない/実装が軽い(Redux不要)/オブジェクトをそのまま渡せる | リロードで消える/直アクセス・URL共有で再現できない(遷移元前提)/戻る, 進むで想定外になり得る | 「直前の遷移でだけ必要」な一時データ(確認画面へ渡す、編集画面の初期値、画面遷移理由のフラグなど)/URLに載せるほどでもなくグローバルに持つほどでもない値 |