Study & Practice

北海道札幌市のプログラマによる技術とか雑記のブログ

ReactRouter~子コンポーネントでthis.porps.history.push()がTypeError: Cannot read property 'push' of undefinedになる

そこそこハマったけど意外と情報が出てこなかったので記録

react等のバージョンは以下(package.json)

{
    "react": "^16.13.1",
    "react-dom": "^16.13.1",
    "react-router-dom": "^5.2.0",
    "react-scripts": "3.4.3"
}

今回発生したパターンの発生条件としては子コンポーネントにpropsを渡すためにcomponentではなくrenderを使ったことで、解決策としては子コンポーネントのexport時にwithRouterを使うことでした

以下の手順でサンプルの環境を作りました

npm create-react-app trial-router
cd trial-router
npm install react-router-dom

src/App.jsを以下の内容に更新

import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
import Base from './BaseComponent';
import Moved from './MovedComponent';

function App() {
  return (
    <div className="App">
      <h1>HOME</h1>
      <BrowserRouter>
        <Route
          exact path="/"
          render={() =>
            <Base text={"Base"} />
          }
        />
        <Route
          path="/moved"
          render={() =>
            <Moved text={"Moved"} />
          }
        />
      </BrowserRouter>
    </div>
  );
}
export default App;

src/ChildComponent.jsを以下の内容で作成

import React from 'react';

class Base extends React.Component {
  move = () => {
    this.props.history.push("/moved");
  }
  render() {
    return (
      <>
        <div>{this.props.text}</div>
        <button onClick={this.move}>
          Move
        </button>
      </>
    );
  }
}
export default Base);

src/MovedComponent.jsを以下の内容で作成

import React from 'react';
class Moved extends React.Component {
  move = () => {
    this.props.history.push("/");
  }
  render() {
    return (
      <>
        <div>{this.props.text}</div>
        <button onClick={this.move}>
          Back
        </button>
      </>
    );
  };
}

export default Moved;

ファイルを作成して

npm start

すると以下のようなページができるはず
f:id:carametal:20201008153129p:plain
このページでMoveボタンを押すと
f:id:carametal:20201008153217p:plain
みごと「TypeError: Cannot read property 'push' of undefined」が発生する

解決策としてはChildComponent.jsとMovedComponent.jsをそれぞれ以下のように変更する
v

// ChildComponent.js
import React from 'react';
// withRouterをimport
import { withRouter } from 'react-router-dom';

class Base extends React.Component {
  move = () => {
    this.props.history.push("/moved");
  }
  render() {
    return (
      <>
        <div>{this.props.text}</div>
        <button onClick={this.move}>
          Move
        </button>
      </>
    );
  }
}
// withRouterの引数にBaseを指定
export default withRouter(Base)
// MovedComponent.js
import React from 'react';
// withRouterをimport
import { withRouter } from 'react-router-dom';

class Moved extends React.Component {
  move = () => {
    this.props.history.push("/");
  }
  render() {
    return (
      <>
        <div>{this.props.text}</div>
        <button onClick={this.move}>
          Back
        </button>
      </>
    );
  };
}
// withRouterの引数にMovedを指定
export default withRouter(Moved);

2ファイルとも修正は同じ

withRouterを追加したらボタンを押すことでChildとMovedを行き来できるはず

ちなみにクラス構文を使わずにReact Hooksを使っても同様のエラーが発生した。

コンポーネントにpropsを渡しつつhistory.push()するときはwithRouterは必須みたいですね