简体   繁体   中英

Prevent react-router history.push from reloading current route

I'm taking my first steps with react-router .

I'm currently using the hashHistory for development purposes and I'm performing 'manual' navigation. That is to say, I'm not using Link and I'm invoking history.push('/some/route'); in order to navigate (in response to plain old clicks on anchor tags).

What I'm noticing is that, even when I'm already on the target route, react-router will re-render the relevant target component every time history.push('/target/route'); is invoked: On every push('/target/route') :

  • the fragment part of the URL remains #/target/route
  • the query string part of the URL changes to ?_k=somethingRandom
  • the target component re-renders

I would like for that re-rendering to not happen - I actually expected history.push to be a no-op when I'm already at the route that I'm attempting to push .

I'm apparently missing something, as this is not what's happening. Funnily enough I'm seeing posts from people who are trying to achieve the behaviour that I'd like to get rid of - they'd like to 'refresh' a route without leaving it, so to speak. Which looks pretty much like the opposite problem :).

Could you enlighten me as to what it is I'm misunderstanding and how I would achieve the desired behaviour? Is this perhaps something that would go away if (when) I switch to browserHistory ?

My guess is that your component re-renders because something in your prop changes when you make a router push. I suspect it might be the action or key properties of prop.location . You could always check all the values of prop during each render to see what changes.

You can solve this issue by comparing your old route path with the new one in the shouldComponentUpdate life-cycle method. If it hasn't changed you are on the same route, and you can prevent the re-rendering by returning false . In all other cases, return true . By default this always returns true.

shouldComponentUpdate: function(nextProps, nextState) {
  if(this.props.route.path == nextProps.route.path) return false;
  return true;
}

You'll have to make further checks as well as this will prevent your component from updating on state updates within the component as well, but I guess this would be your starting point.

Read more about shouldComponentUpdate on the official react docs page.

Use this as an opportunity to return false when you're certain that the transition to the new props and state will not require a component update.

I will give this a shot...

If you land here and looking to change your URL (for sharing purposes for example) then RR docs already has the solution described. Just make sure you do not use the history within the component (ie this.props.history.push() )as you will be (as expected) routed to the target. You are however allowed to access your browser history without any interference with the component's history .

Following tested only on Chrome

// history.js
import { createBrowserHistory } from 'history'
export default createBrowserHistory()

and then from your XYZ component

// XYZ.js
import React from 'react';
import history from './history'

class XYZ extends React.Component {
    _handleClick() {
        // this should not cause rerender and still have URL change 
        history.push("/someloc");
    }
    render() {
        return(
          <button onClick={this._handleClick.bind(this)}>test </button>
        )
    }
}

Hope it helps someone else.

I have the same issue and i find the (dumb) solution.

You just have a <button> (button by default is type=submit ) or something similar (form, submit.... etc) thats is reloading the page like a html <form method=GET ...> .

Check it in your code, and remove it.

PD: _k=somethingRandom > this is just the value inputs (or the button) that you are sending in the form.

I think the easier workaround maybe replacing the Route with our own route

import { Route, withRouter } from "react-router-dom";

function MyRoute({ key, path, exact, component: Component, history }) {
  let lastLocation = null;
  return (
    <Route
      key={key}
      path={path}
      exact={exact}
      render={(props) => {
        history.listen((location) => {
          lastLocation = location;
        });

        // monkey patching to prevent pushing same url into history stack
        const prevHistoryPush = history.push;
        history.push = (pathname, state = {}) => {
          if (
            lastLocation === null ||
            pathname !==
              lastLocation.pathname + lastLocation.search + lastLocation.hash ||
            JSON.stringify(state) !== JSON.stringify(lastLocation.state)
          ) {
            prevHistoryPush(pathname, state);
          }
        };
        return <Component {...props} />;
      }}
    />
  );
}

export default withRouter(MyRoute);

We use this as a wrapper for actual Route of react-router-dom and it works perfectly for me. for more please refer here

In App.js :

shouldComponentUpdate(nextProps, nextState) {
  return nextProps.location.search === this.props.location.search
}

tsx sample

import {createBrowserHistory} from 'history';  
export const history = createBrowserHistory();


ReactDOM.render(
  <Router history={history}>
    <App/>
  </Router>,
  document.getElementById("root")
);

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