React×Typescriptでページ遷移時に値を渡す方法

2021/11/26

(最終更新: 2021/11/26

はじめに

React×Typescript でルーティングする際、前画面の値を次の画面に渡す方法を解説する。

いろいろ調べたが、「これが正解」というのが出てこない。私が理解した方法の解説となる。

今回は4つの方法を解説する。

4つの解決策

  1. URL パラメータを使う(パスパラメータ)
  2. URL パラメータを使う(クエリパラメータ)
  3. <Link>コンポーネントに state を渡す
  4. useHistory()を使う

React-Router-Dom のバージョンについて(2022/4/23 追記)

本記事は react-router-dom のバージョン 5 を利用しています。 バージョン 6 からは、useHistory ではなくて useNavigate が推奨となっているようなので注意して下さい。

前提

以下の記事で作成したアプリを利用して検証する。

https://tomiko0404.hatenablog.com/entry/2021/11/23/react-router-dom-routing

今回は react-router-dom のバージョン 5 を利用している。

URL パラメータで渡す方法(パスパラメータ)

<Route>に渡すpathに以下のように:[パラメータ名]を記載することで、パスパラメータとして扱える。

<Route path="mapage/mypage-detail/:id" render={() => <MyPageDetail />} />

パラメータが任意の場合は?をつける。

<Route path="mapage/mypage-detail/:id?" render={() => <MyPageDetail />} />

期待する動作

  • localhost:8000にアクセスすると Home ページが表示される。(前回実装)
  • localhost:8000/mypageにアクセスすると MyPage が表示される。(前回実装)
  • localhost:8000/mypage/mapage-detailにアクセスすると MyPageDetail が表示される。(今回のポイント)
  • localhost:8000/mypage/mapage-detail/100というように、/100の部分にパスパラメータを入れてアクセスすると MyPageDetail が表示され、画面上にもパラメータの値が表示される。(今回のポイント)
  • 上部のリンクは常に表示される。(前回実装)

期待する動作 期待する動作

ソースコード

ルーティング側の設定(パスパラメータを含むルーティング)

前回の記事で

<Route path={`${url}/mypage-detail`} render={() => <MyPageDetail />} />

としていた部分を

<Route path={`${url}/mypage-detail/:id?`} render={() => <MyPageDetail />} />

と書き換える。

Router.tsx全体

import { Switch, Route } from "react-router-dom"
import { Home } from "../pages/Home"
import { MyPage } from "../pages/MyPage"
import { MyPageDetail } from "../pages/MyPageDetail"

export const Router = () => {
  return (
    <Switch>
      <Route exact path="/" render={() => <Home />} />
      <Route
        path="/mypage"
        render={(props) => {
          console.log(props)
          const url = props.match.url
          return (
            <Switch>
              <Route exact path={url} render={() => <MyPage />} />
              <Route
                path={`${url}/mypage-detail/:id?`}
                render={() => <MyPageDetail />}
              />
            </Switch>
          )
        }}
      />
    </Switch>
  )
}

今回はidを任意としているため、localhost:8000/mypage/mapage-detailのようにidを入れなくても、ちゃんと<MyPageDetail />が表示される。

idを必須とすればlocalhost:8000/mypage/mapage-detailは表示されない(ルーティングから外れる)。

パスパラメータの利用

useParamsという Hooks が提供されており、パスパラメータを利用できる。 typescript では以下のように利用する。

MyPageDetail.tsx

import { useParams } from "react-router";

type Params = {
    id?: string;
};
export const MyPageDetail = () => {
    const { id } = useParams<Params>();

    return (
        <>
            <h1>MyPageDetail</h1>
            <h2>idは{id}</h2>
        </>
    );
};

idは URL パラメータから渡されてきた値なので string 型になる。

パスが文字列である件

ちなみにこちらの記事で、「typescriptを使っていても結局Router.tsxのpathを文字列で定義している以上、安全じゃないよね」 ということが書かれている。

確かに、間違えてidを間違えてibとしてしまい、path={`${url}/mypage-detail/:ib?`}のように書いたとしてもコンパイルエラーにならないため、この点はバグを生みそう。 Template Literal Types などを使って解決する必要があるらしい。

URL パラメータで渡す方法(クエリパラメータ)

クエリパラメータの場合は、<Route>の props にあるlocationを利用する。

useLocation()を利用することで、location オブジェクトを取得できる。

const { search } = useLocation()
const queryParams = new URLSearchParams(search)
const name = queryParams.get("name") // nameはクエリパラメータ名の例

Route の props の history を利用する Routeのpropsのhistoryを利用する

期待する動作

  • localhost:8000/mypage/mapage-detail/100?name=taro&age=20というように、パスパラメータ、クエリパラメータを入れてアクセスすると、MyPageDetail が表示される。画面上にもパラメータの値が表示される。(今回のポイント)
  • それ以外はパスパラメータの節と同様。

ソースコード

ルーティング側の設定

パスパラメータのときと同じ。 クエリパラメータはルーティングで設定する必要はない。

クエリパラメータの利用

利用する側のコンポーネントのソースは以下。

MyPageDetail.tsx
import { useLocation, useParams } from "react-router";

type Params = {
    id?: string;
};

export const MyPageDetail = () => {
    const { id } = useParams<Params>();
    const { search } = useLocation();
    const queryParams = new URLSearchParams(search);    const name = queryParams.get("name");    const age = queryParams.get("age");
    return (
        <>
            <h1>MyPageDetail</h1>
            <h2>idは{id}</h2>
            <h2>nameは{name}</h2>
            <h2>ageは{age}</h2>
        </>
    );
};

<Link>から state で渡す方法

期待する動作

  • localhost:8000/mypage/mapage-detail/100?name=taro&age=20というように、パスパラメータ、クエリパラメータを入れてアクセスするのは URL パラメータの時と同様。
  • MyPage で定義した値(login、group)が MyPageDetail で利用でき、画面に表示できる。(今回のポイント)
  • それ以外はパスパラメータの節と同様。

location オブジェクトの state

useLocation()で取得できるstateオブジェクトを利用する。

先ほど、URLパラメータのみ設定していたとき、useLocationの中身は
hash: ""
key: "6sdwd9"
pathname: "/mypage/mapage-detail/20/"
search: "?name=taro&age=20"
state: undefined

となっていた。

ここで使っていなかったstate部分を使う。

Route の props の中身(location)

Routeのpropsの中身(location)

<Link>で state を設定する

MyPage.tsxの中で

<Link to="/mypage/mapage-detail/20/?name=taro&age=20">
  link to MyPageDetail
</Link>

と定義している部分は、location のオブジェクト名を使って以下のように書き換えられる。

<Link
  to={{ pathname: "/mypage/mapage-detail/20/", search: "?name=taro&age=20" }}
>
  link to MyPageDetail
</Link>

こうすると、location の中身は先ほどと同じになる。

ここに state を追加する。

<Link
  to={{
    pathname: "/mypage/mapage-detail/20/",
    search: "?name=taro&age=20",
    state: myState,
  }}
>
  link to MyPageDetail
</Link>

このとき、locatoin オブジェクトの state に値が設定されたことがわかる。

stateが追加された

ソースコード

遷移元

ポイントは<Link>タグにstateを設定していることのみ。事前に定義したmyStateを設定。

Mypage.tsx
import { Link } from "react-router-dom";

type MyState = {
    login: boolean;
    group: number;
};

export const MyPage = () => {
    // stateに設定する値(仮)
    const myState: MyState = {
        login: true,
        group: 1,
    };

    return (
        <div>
            <h1>Mypage</h1>
            <Link
                to={{
                    pathname: "/mypage/mapage-detail/20/",
                    search: "?name=taro&age=20",
                    state: myState,
                }}
            >
                link to MyPageDetail
            </Link>
        </div>
    );
};

遷移先(利用側)

useLocation()で取得できる location オブジェクトから state オブジェクトを取り出す。

const { state } = useLocation<MyState>();

その際、state オブジェクトの型をとして定義(今回はMyState)することが必要。

MyPageDetail.tsx
import { useLocation } from "react-router";

type MyState = {
    login: boolean;
    group: number;
};

export const MyPageDetail = () => {
    const { state } = useLocation<MyState>();
    const { group, login } = state;

    return (
        <>
            <h1>MyPageDetail</h1>
            <h2>group:{group}</h2>
            <h2>login:{String(login)}</h2>
        </>
    );
};

useHistory()を使う方法

locationmatchと横並びで、<Route>コンポーネントの props に存在するhistoryオブジェクトを使う。 history はuseHistory()という Hooks 関数で利用することができる。

const onClick = () => history.push("/mypage/mypage-detail", myState)
return <button onClick={onClick}>MyPageDetailへ</button>

history オブジェクトの中身

historyの中身

history.push

history.push( path, state)は第一引数に path、第二引数に state を持つ関数。

公式ホームページに

新しいエントリを履歴スタックにプッシュします

React Router: Declarative Routing for React.js

とあるとおり、ページ遷移の履歴(history)に対して path で指定した宛先を追加する機能。同時に、履歴の最新となる「path で指定した宛先」に遷移できる。

history.goback

hisoty.goback()は、ブラウザの「戻る」と同じ動きをする関数。

期待する動作

<Link>から state で渡す方法と同様だが、今回は<Link>ではなくボタンのonClick関数に遷移動作を定義する。

ソースコード

遷移元

ポイントは以下のようにonClick関数を定義する部分。

const history = useHistory()
const onClick = () => history.push("/mypage/mypage-detail", myState)
import { useHistory } from "react-router-dom"

type MyState = {
  login: boolean,
  group: number,
}

export const MyPage = () => {
  // stateに設定する値(仮)
  const myState: MyState = {
    login: true,
    group: 1,
  }

  const history = useHistory()
  const onClick = () => history.push("/mypage/mypage-detail", myState)
  console.log(history)

  return (
    <div>
      <h1>Mypage</h1>
      <div>
        <button onClick={onClick}>MyPageDetailへ</button>
      </div>
    </div>
  )
}

遷移先(利用側)

<Link>から state で渡す方法のときと全く同じ。

const { state } = useLocation<MyState>();

のようにuseLocaton()を利用することで state を取得できる。

history と location の違い

以下の記事によれば、

「history は変わらないもの。location はページ遷移のたびに変わるもの。」

参考資料

react-routerで現在のlocationを取得する2種類の方法の使い分け方 - uhyo/blog

おわりに

以下 4 つの方法で、ページ遷移先に値を渡す方法を理解した。

  • URL パラメータを使う
    • パスパラメータ
    • クエリパラメータ
  • <Link>コンポーネントに state を渡す
  • useHistory()を使う

パスが文字列である件でも書いたように、<Link>を使う場合でもhistory.push()を使う場合でも、パスやパラメータの設定が文字列となっており、タイプミスを引き起こしそうな点は気になるので引き続き調査する。

関連記事

react-router-dom を使ったルーティング方法

参考文献

以上です。



個別連絡はこちらへ→Twitterお問い合わせ

プロフィール

プロフィールイメージ

はち子

事業会社のシステム部門に異動して4年目の会社員。システム企画/要件定義/システムアーキテクチャ等。

Twitter→@bun_sugi

過去の記事について

はてなブログに掲載の記事(主にプログラミングメモ)についてはこちらに掲載しております。(本ブログに移行中)

タグ一覧

関連記事

Copyright© 2022, エンジニアを目指す日常ブログ

お問い合わせ|プライバシーポリシー