this.props.history.push works in some components and not others

65,339

Solution 1

You answered your question in your question.

As you can see, the components are virtually exactly the same except that the child one is inside the parent one.

The very fact that the component is nested one further is your issue. React router only injects the routing props to the component it routed to, but not to the components nested with in.

See the accepted answer to this question. The basic idea is that you now need to find a way to inject the routing props to the child component. You can do that by wrapping the child in a HOC withRouter.

export default withRouter(connect(mapStateToProps, matchDispatchToProps)(ChildView));

I hope this helps.

Solution 2

Using withRouter is fine, an alternative option is to pass the history as a prop from parent to child (without using withRouter), e.g:

Parent

<SignupForm history={this.props.history}/>

Child

this.props.history.push('/dashboard');

Solution 3

You Can try this

this.context.history.push('/dashboard')

or pass the history as a prop from parent to child component like

parent

<SignupForm history={this.props.history}/>

child

this.props.history.push('/dashboard');

or withRouter

import { withRouter } from "react-router";
export default withRouter(connect(mapStateToProps, {
    ...
})(Header));

Solution 4

This is the problem of Nested Components. I was facing this issue in Header component. I wanted to redirect the page to home page on clicking the logo.

this.props.history.push won't work in nested components.

So Solution is to use withRouter .

To Use withRouter just copy paste Below 2 lines in Header(You can use it in your component) :

import { withRouter } from "react-router";
export default withRouter(connect(mapStateToProps, {
    ...
})(Header));
Share:
65,339
Rbar
Author by

Rbar

Hi there. I'm a lifelong learner, nerd, and curious person with a passion for learning and sharing knowledge with others. I hope I can help you out and vice versa.

Updated on July 10, 2022

Comments

  • Rbar
    Rbar almost 2 years

    I am trying to programmatically change pages using browserHistory.push. In one of my components, but not in a component that I embedded inside of that one.

    Does anyone know why my project is throwing the error below only for the child component but not for the parent component?

    Cannot read property 'push' of undefined

    Parent Component

    import React, {Component} from 'react';
    import { browserHistory } from 'react-router';
    import ChildView from './ChildView';
    
    class ParentView extends Component {
    
    constructor(props) {
        super(props);
        this.addEvent = this.addEvent.bind(this);
      }
    
    changePage() {
        this.props.history.push("/someNewPage");
      }
    
    render() {
        return (
          <div>
            <div>
              <button onClick={this.changePage}>Go to a New Page!</button>
            </div>
    
            <ChildView />  // this is the child component where this.props.history.push doesn't work 
    
          </div>
        );
      }
    }
    
    function mapStateToProps(state) {
        return {
            user: state.user
        };
    }
    
    function matchDispatchToProps(dispatch) {
        return bindActionCreators({
          setName: setName
        }, dispatch)
    }
    
    export default connect(mapStateToProps, matchDispatchToProps)(ParentView);
    

    Child Component

        import React, {Component} from 'react';
        import { browserHistory } from 'react-router';
    
        class ChildView extends Component {
    
        constructor(props) {
            super(props);
            this.addEvent = this.addEvent.bind(this);
          }
    
        changePage() {
            this.props.history.push("/someNewPage");
          }
    
        render() {
            return (
              <div>
                <div>
                  <button onClick={this.changePage}>Go to a New Page!</button>
                </div>
    
              </div>
            );
          }
        }
    
        function mapStateToProps(state) {
            return {
                user: state.user
            };
        }
    
        function matchDispatchToProps(dispatch) {
            return bindActionCreators({
              setName: setName
            }, dispatch)
        }
    
        export default connect(mapStateToProps, matchDispatchToProps)(ChildView);
    

    Router

    // Libraries
    import React from 'react';
    import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
    import { browserHistory } from 'react-router';
    
    // Components
    import NotFound from './components/NotFound';
    import ParentView from './components/ParentView';
    import ChildView from './components/ChildView';
    import SomeNewPage from './components/SomeNewPage';
    
    // Redux
    import { Provider } from 'react-redux';
    import {createStore} from 'redux';
    import allReducers from './reducers';
    
    const store = createStore(
      allReducers,
      window.devToolsExtension && window.devToolsExtension()
    );
    
    const routes = (
        <Router history={browserHistory}>
          <div>
            <Provider store={store}>
                <Switch>
                  <Route path="/" exact component={Home} />
                  <Route path="/parentView" component={ParentView} />
                  <Route path="/someNewPage" component={SomeNewPage} />
                  <Route path="/childView" component={ChildView} />
                  <Route component={NotFound} />
                </Switch>
            </Provider>
          </div>
        </Router>
    );
    
    export default routes;
    

    As you can see, the components are virtually exactly the same except that the child one is inside the parent one.

    Note I have tried these approaches but they do not resolve the issue for me:

  • Rbar
    Rbar almost 7 years
    Since I still want to continue connecting my component to redux as well, do you know how you would you combine my export default connect(mapStateToProps, matchDispatchToProps)(ChildView); line and the recommended export default withRouter(Child) line from your link?
  • Rbar
    Rbar almost 7 years
    Just to answer the question for others, export default withRouter(connect(mapStateToProps, matchDispatchToProps)(ChildView)); works like a charm. Huge thanks, @promisified!
  • learner
    learner over 6 years
    What about defining a function in routed component and passing as props to its child. Now calling this function as this.props we can get the expected behaviour.
  • Brian
    Brian about 6 years
    Is this definitely a safe/good practice? This was the solution I discovered for my app before finding this question. My child component didn't need full routing props, just the ability to push to history, and this seems like a pretty clean solution to me. (Sorry I'm super new to React!)
  • Danish
    Danish about 5 years
    Good solution, the problem is that when we are in nested components, we need to pass props of history from the particular parent that is connected to routing.
  • SuperUberDuper
    SuperUberDuper about 5 years
    what about if you want to unit test the component, not the container?
  • Aboobakkar P S
    Aboobakkar P S about 5 years
    Very Useful answer.
  • Prashant Yalatwar
    Prashant Yalatwar almost 5 years
    just pass component in the withRouter like this: export default withRouter(App)
  • Prashant Yalatwar
    Prashant Yalatwar almost 5 years
    <Button onClick={() => this.props.history.push("/page_name")}>ClickHere</Button> sometimes this also matters. Try like this ..