[React Router] 直接ルーティングしていない孫コンポーネントからhistoryにアクセス可能にする(WithRouter)

2022.02.14

こんにちは、CX事業本部 IoT事業部の若槻です。

今回は、React Router(react-router-dom)でWithRouterを使って、直接ルーティングしていない孫コンポーネントからhistoryにアクセス可能にしてみました。

やってみた

具体的にやりたいこととしてはこんな感じです。ルーティング先の子コンポーネント内で通常のタグコンポーネントとして使用されている孫コンポーネント内でhistoryにアクセスできるようにしてみます。

<親コンポーネント>

↓ ルーティング ←ここでprops.history.pushで値(state.userName)を渡す

<子コンポーネント>

↓ 通常のタグコンポーネントとして使用

<孫コンポーネント> ←ここでstate.userNameにアクセスする

実装

次のようなコンポーネント構成で確認してみます。

  • 親コンポーネント:Home
  • 子コンポーネント:MyProfile
  • 孫コンポーネント:Header

親コンポーネントHomeでは、props.history.pushで子コンポーネントMyProfileへのルーティングを行っています。その際にstate.userNameを渡しています。

src/Home.tsx

import React from 'react';
import { RouteComponentProps } from 'react-router-dom';

const Home = (props: RouteComponentProps) => {
  return (
    <div>
      <h1>Home page</h1>
      <ul>
        <li>
          <button
            onClick={() => {
              props.history.push({
                pathname: '/profile',
                state: { userName: 'わかつき' }
              });
            }}
          >
            Go to my profile
          </button>
        </li>
      </ul>
    </div>
  );
};

export default Home;

MyProfileでは、孫コンポーネントHeaderを、ルーティングではなく、通常のタグコンポーネントとして使用しています。

MyProfile.tsx

import React from 'react';
import Header from './header/Header';
import { RouteComponentProps } from 'react-router-dom';

const MyProfile: React.FC<RouteComponentProps> = (
  props
) => {
  return (
    <div>
      <Header />
      <h1>This is my profile</h1>
      <ul>
        {props.location.state && (
          <li>
            <button
              onClick={() => {
                props.history.goBack();
              }}
            >
              Back
            </button>
          </li>
        )}
      </ul>
    </div>
  );
};

export default MyProfile;

孫コンポーネントHeaderです。props.location.state.userNameによりhistoryにアクセスするようにしていますが、この場合は正常に動きません。

src/header/Header.tsx(エラー)

import React from 'react';
import './Header.css';
import { RouteComponentProps } from 'react-router-dom';

type Props = RouteComponentProps<
  {},
  {},
  { userName: string }
>;

const Header: React.FC<Props> = (props) => {
  return (
    <div className="header">
      <h1>ヘッダー</h1>
      {props.location.state &&
        props.location.state.userName}
    </div>
  );
};

export default Header;

props.location.state.userNameへのアクセスがundefinedとなりエラーとなってしまいます。

MyProfile側でもHeaderの読み込みがエラーとなってしまいます。

そこでWithRouterを使用することにより、直接props.history.pushによるルーティングをしていないコンポーネントでもhistoryにアクセスできるようになります。

src/header/Header.tsx

import React from 'react';
import './Header.css';
import {
  RouteComponentProps,
  withRouter
} from 'react-router-dom';

type Props = RouteComponentProps<
  {},
  {},
  { userName: string }
>;

const Header: React.FC<Props> = (props) => {
  return (
    <div className="header">
      <h1>ヘッダー</h1>
      {props.location.state &&
        props.location.state.userName}
    </div>
  );
};

export default withRouter(Header);

デモ

Home Page/でボタンをクリックすると、親コンポーネントHomeからのルーティング時にhistoryにより渡したuserNameに、孫コンポーネントHeaderからアクセスできています。

子コンポーネントMyProfileのページ/prifileに直接アクセスすると、ルーティングは行われていないので、HeaderコンポーネントからuserNameにはアクセスできず、表示はされません。

参考

以上