52

I'm trying to route programatically using this.props.history.push(..) but it doesn't seem to work.

Here's the router:

import {
 BrowserRouter as Router,
 Route
} from 'react-router-dom';

<Router>
 <Route path="/customers/" exact component={CustomersList} />
 <Route path="/customers/:id" exact component="{Customer} />
</Router>

In CustomerList, a list of customers is rendered. Clicking on a customer (li) should make the application route to Customer:

import { withRouter } from 'react-router'

class Customers extends Component {
  static propTypes = {
    history: PropTypes.object.isRequired
  }

 handleCustomerClick(customer) {
   this.props.history.push(`/customers/${customer.id}`);
 }

 render() {
   return(
    <ul>
      { this.props.customers.map((c) =>
        <li onClick={() => this.handleCustomerClick(c)} key={c.id}>
          {c.name}
        </li> 
    </ul>
  )

 }
}

//connect to redux to get customers

CustomersList = withRouter(CustomersList);
export default CustomersList;

The code is partial but illustrates perfectly the situation. What happens is that the browser's address bar changes accordingly to history.push(..), but the view does not update, Customer component is not rendered and CustomersList is still there. Any ideas?

Vendetta
  • 2,078
  • 3
  • 13
  • 31
Andrea Ongaro
  • 521
  • 1
  • 4
  • 3
  • Hi, I do believe that what is really happening is that both components match and both components are being rendered. Could you check if both component are executing their respective `componentDidMount` please? – Facundo La Rocca Jun 01 '17 at 16:13
  • @FacundoLaRocca They have `exact` on both routes – Andrew Li Jun 01 '17 at 16:14
  • Yes, I know, But I've faced similar issues. That's why I'm asking. – Facundo La Rocca Jun 01 '17 at 16:15
  • 1
    You can take a look at [these examples](https://medium.com/@pshrmn/a-simple-react-router-v4-tutorial-7f23ff27adf). I guess you have to use a `Switch` component to match what you want. – Facundo La Rocca Jun 01 '17 at 16:22
  • @FacundoLaRocca like he suggested , i think the problem is that the two routes have the same name , i countered the same problem , just change customers to customer , and give it a try – The pyramid Jan 07 '19 at 15:28
  • I was missing `exact` on my `Router` – Andrew Allen Jun 01 '21 at 03:25

15 Answers15

26

So I came to this question hoping for an answer but to no avail. I have used

const { history } = this.props;
history.push("/thePath")

In the same project and it worked as expected. Upon further experimentation and some comparing and contrasting, I realized that this code will not run if it is called within the nested component. Therefore only the rendered page component can call this function for it to work properly.

Find Working Sandbox here

  • history: v4.7.2
  • react: v16.0.0
  • react-dom: v16.0.0
  • react-router-dom: v4.2.2
user35443
  • 6,309
  • 12
  • 52
  • 75
  • 1
    Given how fast react-router, (and for that matter, the whole JS ecosystem moves), it will improve your answer if you include what version you're on, and your answer will be amazing if you provide an [MCVE](https://stackoverflow.com/help/mcve). Services like [JSFiddle](https://jsfiddle.net/) should help with that. – Adam Barnes Feb 27 '18 at 17:27
  • 1
    I get history.push( is not a function – SuperUberDuper Mar 26 '19 at 10:35
  • IMHO this is the same as this.pros.history.pus(). You just put it into other variable. both undefined to me.... – Subdigger Jan 14 '21 at 11:46
14

It seems things have changed around a bit in the latest version of react router. You can now access history via the context. this.context.history.push('/path')

Also see the replies to the this github issue: https://github.com/ReactTraining/react-router/issues/4059

cgat
  • 3,689
  • 4
  • 24
  • 38
  • 1
    A better way to do this is to use `withRouter` HOC. https://github.com/ReactTraining/react-router/blob/master/packages/react-router/docs/api/withRouter.md – cgat Sep 28 '17 at 13:27
9

You can try to load the child component with history. to do so, pass 'history' through props. Something like that:

  return (
  <div>
    <Login history={this.props.history} />
    <br/>
    <Register/>
  </div>
)
Mr. Nun.
  • 775
  • 11
  • 29
Taha A.
  • 208
  • 3
  • 9
9

For me (react-router v4, react v16) the problem was that I had the navigation component all right:

import { Link, withRouter } from 'react-router-dom'

class MainMenu extends Component {

  render() {
    return (
            ...
            <NavLink to="/contact">Contact</NavLink>
            ...
    );
  }
}

export default withRouter(MainMenu);

Both using either

to="/contact" 

or

OnClick={() => this.props.history.push('/contact')}; 

The behavior was still the same - the URL in browser changed but wrong components were rendered, the router was called with the same old URL.

The culprit was in the router definition. I had to move the MainMenu component as a child of the Router component!

// wrong placement of the component that calls the router
<MainMenu history={this.props.history} />
<Router>
   <div>
     // this is the right place for the component!
     <MainMenu history={this.props.history} />
     <Route path="/" exact component={MainPage} />
     <Route path="/contact/" component={MainPage} />
   </div>
</Router>
Jan Matousek
  • 956
  • 3
  • 13
  • 15
7

You can get access to the history object's properties and the closest 's match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.

import React, { Component } from 'react'
import { withRouter } from 'react-router'; 
// you can also import "withRouter" from 'react-router-dom';

class Example extends Component {
    render() {
        const { match, location, history } = this.props
        return (
            <div>
                <div>You are now at {location.pathname}</div>
                <button onClick={() => history.push('/')}>{'Home'}</button>
            </div>
        )
    }
}


export default withRouter(Example)
Hemant
  • 170
  • 2
  • 11
3

Seems like an old question but still relevant.

I think it is a blocked update issue.

The main problem is the new URL (route) is supposed to be rendered by the same component(Costumers) as you are currently in (current URL).

So solution is rather simple, make the window url as a prop, so react has a chance to detect the prop change (therefore the url change), and act accordingly.

A nice usecase described in the official react blog called Recommendation: Fully uncontrolled component with a key.

So the solution is to change from render() { return( <ul>

to render() { return( <ul key={this.props.location.pathname}>

So whenever the location changed by react-router, the component got scrapped (by react) and a new one gets initiated with the right values (by react).

Oh, and pass the location as prop to the component(Costumers) where the redirect will happen if it is not passed already.

Hope it helps someone.

arcol
  • 1,510
  • 1
  • 15
  • 20
3

I had similar symptoms, but my problem was that I was nesting BrowserRouter


Do not nest BrowserRouter, because the history object will refer to the nearest BrowserRouter parent. So when you do a history.push(targeturl) and that targeturl it's not in that particular BrowserRouter it won't match any of it's route, so it will not load any sub-component.

Solution

Nest the Switch without wrapping it with a BrowserRouter


Example

Let's consider this App.js file

<BrowserRouter>
  <Switch>
    <Route exact path="/nestedrouter" component={NestedRouter}  />
    <Route exact path="/target" component={Target}  />
  </Switch>
</BrowserRouter>

Instead of doing this in the NestedRouter.js file

<BrowserRouter>
  <Switch>
    <Route exact path="/nestedrouter/" component={NestedRouter}  />
    <Route exact path="/nestedrouter/subroute" component={SubRoute}  />
  </Switch>
</BrowserRouter>

Simply remove the BrowserRouter from NestedRouter.js file

  <Switch>
    <Route exact path="/nestedrouter/" component={NestedRouter}  />
    <Route exact path="/nestedrouter/subroute" component={SubRoute}  />
  </Switch>
Community
  • 1
  • 1
Madacol
  • 3,611
  • 34
  • 33
1

Let's consider this scenario. You have App.jsx as the root file for you ReactJS SPA. In it your render() looks similar to this:

<Switch>
    <Route path="/comp" component={MyComponent} />
</Switch>

then, you should be able to use this.props.history inside MyComponent without a problem. Let's say you are rendering MySecondComponent inside MyComponent, in that case you need to call it in such manner:

<MySecondComponent {...props} />

which will pass the props from MyComponent down to MySecondComponent, thus making this.props.history available in MySecondComponent

SimantoR
  • 11
  • 1
0

You need to export the Customers Component not the CustomerList.

    CustomersList = withRouter(Customers);
    export default CustomersList;
0

I see that you are using a class component but in case you decide to switch to functional component or encountered the same issue with a functional component in your application, you can fix this issue by using the "useHistory" hook API by react-router-dom.

Example of usage:

import { useHistory } from "react-router-dom";

const Customers = ({customer}) => {
   let history = useHistory();

   const handleCustomerClick = (customer) => {
     history.push(`/customers/${customer.id}`);
   }

  return (
    //some JSX here
  );
};

You may find the official documentation here: https://reactrouter.com/web/api/Hooks/usehistory

Wani
  • 278
  • 3
  • 8
0

Beginner's mistake when working with routing is the importance of using withRouter directly with the component and not put any other high order component in between (or at least one that doest not know to push the props.history to its children:

Wrong: export default withRouter(withErrorHandler(Foo));

Correct: export default withErrorHandler(withRouter(Foo));

Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
0

`const navigate=useNavigate();

navigate(/customers/${customer.id}); `

Ahmed amani
  • 101
  • 1
  • 4
-3

Don't use with Router.

handleSubmit(e){
   e.preventDefault();
   this.props.form.validateFieldsAndScroll((err,values)=>{
      if(!err){
        this.setState({
            visible:false
        });
        this.props.form.resetFields();
        console.log(values.username);
        const path = '/list/';
        this.props.history.push(path);
      }
   })
}

It works well.

Pardeep Jain
  • 84,110
  • 37
  • 165
  • 215
YangFang
  • 3
  • 1
-3

You need to bind handleCustomerClick:

class Customers extends Component {
  constructor() {
    super();
    this.handleCustomerClick = this.handleCustomerClick(this)
  }
Yves Dorfsman
  • 2,684
  • 3
  • 20
  • 28
-6
this.props.history.push(`/customers/${customer.id}`, null);
anothernode
  • 5,100
  • 13
  • 43
  • 62
  • 8
    Welcome to Stack Overflow! It would be great if you could add a little bit of an explanation about why you think this call solves the problem asked about in the question. – anothernode Jun 28 '18 at 14:39