简体   繁体   中英

React-router v4 this.props.history.push(...) not working

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?

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

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

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>
)

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>

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)

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.

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>

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

You need to export the Customers Component not the CustomerList .

    CustomersList = withRouter(Customers);
    export default CustomersList;

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

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));

In the react-router-dom v6, the support for history has been deprecated but instead of it navigate has been introduced. if you want to redirect user to a specific page on success of a specific event then follow the steps given below:

Create a file named as withRouter.js, and paste the code given below in this file

import { useNavigate } from 'react-router-dom';

export const withRouter = (Component) => {
  const Wrapper = (props) => {
    const navigate = useNavigate();

    return (
      <Component
        navigate={navigate}
        {...props}
        />
    );
 };

  return Wrapper;
};

Now, in whichever class based component you want to redirect the user to a specific path/component, import the above withRouter.js file there and use this.props.navigate('/your_path_here') function for the redirection.

For your help, a sample code showing the same has been given below:

import React from 'react';
import {withRouter} from '.your_Path_To_Withrouter_Here/withRouter';

class Your_Component_Name_Here extends React.Component{
     constructor(){
       super()
       this.yourFunctionHere=this.yourFunctionHere.bind(this);
    }

    yourFunctionHere()
    {
        this.props.navigate('/your_path_here')
    }

    render()
    {
        return(
            <div>
              Your Component Code Here 
            </div>
        )
    }
}

export default withRouter(Your_Component_Name_Here);

I Hope it solved your issue. If it then do upvote this answer. Thanks!

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.

You need to bind handleCustomerClick :

class Customers extends Component {
  constructor() {
    super();
    this.handleCustomerClick = this.handleCustomerClick(this)
  }
this.props.history.push(`/customers/${customer.id}`, null);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM