React-router: how to reload a route?

Created on 16 Sep 2015  ·  108Comments  ·  Source: ReactTraining/react-router

How do i tell react-router to reload current route?

Most helpful comment

I want the components to re-initialize.
That way they can re-fetch their data that they do on init.

calling window.location.reload() will cause all the scripts to load again,
producing a much larger delay than just refetching all the component's data

All 108 comments

The Router has a refresh method. It might be what you need

That's 0.13 API, @andrefarzat.

Can you be more specific about what you need, @pdeva? For example, when you say "reload current route" do you want all onEnter hooks to fire? Or do you just want to re-render?

The easiest way to reload the entire page is to just window.location.reload().

I want the components to re-initialize.
That way they can re-fetch their data that they do on init.

calling window.location.reload() will cause all the scripts to load again,
producing a much larger delay than just refetching all the component's data

I want the components to re-initialize. That way they can re-fetch their data that they do on init.

This sounds like your responsibility, not ours. We don't prescribe a way for you to load your data.

@mjackson Managing state changes can be quite complex sometimes and we just want to reload the whole page. But window.location.reload is bad solution for SPA, router could handle that easily. For example ui-router (most popular router for angular) has this feature. From ui-router docs: "reload - If true will force transition even if the state or params have not changed, aka a reload of the same state."

For now I use workaround to make 2 history.pushState one after another.

// I just wanted to reload a /messages page
history.pushState(null, '/');
history.pushState(null, '/messages');
  1. You probably want to just respond to prop changes with componentDidUpdate to refetch data when the same route is active but with different parameters
  2. Or, if you really want to refresh data at the same location, you should have a different mechanism than the router. For example, you might do this.fetch inside of comopnentDidMount, which sets state with data. Just call this.fetch again.

I can't think of a use-case where you really want the router to "refresh" at that location.

@ryanflorence Sorry Ryan but you're doing wrong assertions about page I want to reload. It's not even React. Of course it's possible to fetch data again programmatically but that's not easy. There are complex situations where it's much easier to simply render the whole page again and let it fetch it's data.
I gave you example of same functionality exist in angular routers (ui-router, ng-route). And I see I'm not the first one who is asking about reload. #2097 #2038 #2243 #1982

Is that's actually possible to implement in react-router? As I understand I need native dom to re-render, even when virtual dom isn't changed.

Sure it's possible, but our policy is to not answer "how do I do X with React" questions on our issue tracker – please try Stack Overflow or Reactiflux.

If we implemented a "refresh" feature there's nothing we could do except call this.forceUpdate() in Router. So you can just do it yourself.

const Root = React.createClass({
  childContextTypes: {
    refresh: React.PropTypes.func
  },

  getChildContext() {
    return {
      refresh: () => this.forceUpdate()
    }
  },

  render() {
    return <Router .../>
  }
})

// then in any component
contextTypes: {
  refresh: React.PropTypes.func
}

// and then
this.context.refresh()

Note that the lifecycle hooks that are going to be called probably won't trigger your data to reload anyway. Angular blows everything away and reinitializes all of it. React will do a virtual DOM diff. None of your willMount, didMount etc. hooks will be called. But you'll get "already mounted" hooks like componentWillReceiveProps and componentDidUpdate.

There could be 2,000 people asking for this feature to try to reload their data, but React (not React Router) just doesn't work that way.

@taion I meant if that's possible to implement in react-router library, not how can I do that in React.
@ryanflorence thanks for clarification. Now I understand that React simply won't trigger native dom re-render even on forceUpdate() call, because virtual dom isn't changed in my case.

We have set an onClick in every Link that we want to refresh. We check if the to="/???" === the current route, and if so, we just trigger the actions (Redux) we need for each case. It works but it is a bit cumbersome and we are not happy with the solution at all, so we'd be glad to hear if someone came up with a better and more universal solution. In this case, we have to make sure we do the checks for each and every single Link, which is tedious.

@mezod I can completely relate to everything you just mentioned.

@mezod I can completely relate to everything you just mentioned too.

Because of exact explanation of requested feature - could be this issue re-opened?

It might not classify as an issue. probably a feature that would be awesome to have.

I can't think of a use-case where you really want the router to "refresh" at that location.

When you have onEnter hooks and don't want to duplicate business logic, for example.

+1 Need to reload active Route after interface lang was changed..

I also need this feature to ensure that when a user logs out, if they are on a page they shouldn't see, they get redirected to the login page. So, I need the onEnter to be evaluated again.

export default (store) => {
  const requireLogin = (nextState, replace, cb) => {
    const { auth: { user } } = store.getState();
    if (!user) {
      // oops, not logged in, so can't be here!
      replace('/login?returnUrl=' +
        encodeURIComponent(nextState.location.pathname + nextState.location.search));
    }
    cb();
  };
  return (
    <Route path="/" component={App}>
      { /* Home (main) route */ }
      <IndexRoute component={Home} />

      { /* Routes */ }
      <Route path="about" component={About} />
      <Route path="contact" component={Contact} />
      <Route path="register" components={Register} />
      <Route path="login" components={Login} />
      <Route path="forgotpassword" components={ForgotPassword} />
      <Route path="resetpassword" components={ResetPassword} />
      <Route path="confirmemail" components={ConfirmEmail} />

      <Route path="manage" component={Manage} onEnter={requireLogin}>
        <IndexRoute component={ManageIndex} />
        <Route path="changepassword" component={ManageChangePassword} />
        <Route path="logins" component={ManageLogins} />
      </Route>

      { /* Catch all route */ }
      <Route path="*" component={NotFound} status={404} />
    </Route>
  );
};
<Link to={{pathname: this.myCurrentPath(), query: {'newState': 'something'}}}>

When clicking this link, the router doesn't call onEnter for the current route again. Am I missing something in here ? If not, in my humble opinion this is a potential issue with how react-router is built, and here's why:

  • in pure HTML if you click on a link pointing to the current page, the current page gets reloaded
  • with react-router it looks like a decision was made to not reload the current page, maybe for performance reasons or otherwise. This to me makes sense in some cases, don't get me wrong. But forcing all links to the current page to not reload the current Route and therefore not call onEnter makes it unnecessarily harder to reload the current route.

There are a number of ways to get around this issue, but it seems like this behaviour should be supported by the react router

Use onChange

It's new as of 2.1.0.

So I was missing something! Thanks @taion! I'll use this. I'm still running into some minor issues but onChange does fire now, whenever I link to the current route

So the function definition for onChange has a different signature than onEnter and I was using the previousState instead of the nextState. It works now.

@taion :boom:

@Kosmin can you post an example of how you reload component in onChange hook? Or how can I call a method on component attached to route.

@nvartolomei sorry I'm late with the reply, but here it is for anyone running into the same issue. I didn't reload the component, I just fired a redux action to reload the data and I let Redux do its thing and update the component with the new data from the store.

The point of the code below is to illustrate how I've made the React Router use Redux Actions to load data & filter it. For me this approach works because it keeps things clear & simple without breaking any principles either for the React Router or for the Redux architecture.

function initPageData(currentState) {
  // My action creators will pick up the query from here. I'm putting this on the window, to keep things simple
  window.currentQuery = Qs.parse(currentState.location.query) || {};

  // store the current search params for concatenating new searches
  // e.g. if you have pagination on a filtered search, you'll still want to keep the filters when going to a new page
  store.dispatch(dispatch => dispatch(updateSearchParams(window.currentQuery)));

  // this action actually loads all elements via a JSON request.
  store.dispatch(dispatch => dispatch(fetchSelectedRows(window.currentPathname, window.currentQuery)));
}

<Provider store={store}>
      <Router history={history}>
        <Route
          path="/cards"
          component={CardsLayout}
          onEnter={initPageData}
          onChange={loadPageData}
        />
     </Router>
</Provider

Obviously you have to create your fetchSelectedRows and updateSearchParams actions, as well as your store, and import your actions from your redux ActionCreators. Also I didn't include the loadPageData function simply because it does pretty much the same thing as the initPageData, except that it takes 2 parameters instead of 1

@Kosmin how would you do this without redux? namely, without being able to dispatch a global action? i just need my component to be remounted for my componentDidMount hook to fire

I'm not sure I understand the question @davis: do you want to load server-side data without using redux ?

I guess the best way would be to have the same onEnter function that will let the route pass in the right props to your component AFTER making the server call (as opposed to updating the props through the store & reducer of redux)

ah, nevermind i had a separate issue! thanks though :D

You guys can use unmountComponentAtNode

ReactDOM.unmountComponentAtNode(node)

I have a use case for this feature. Currently when I hot reload a module in the browser I change the data that getComponent method of a route depends on to return the relevant component. But after changing the data I need to either 1) make react-router somehow call getComponent again and renders newly generated component or 2) somehow simply reload the page (but ofc without window.location.reload as it wouldn't be hot reload then.).
Currently I'm doing this with a dirty hack that I don't like and is a bit time consuming:

    const location = this.context.store.getState().get('routing').get('locationBeforeTransitions').toJS();
    this.props.dispatch(push('/'));
    this.props.dispatch(push(location));

I should add that as you know neither forceUpdate nor refetching data manually nor other solutions noted above work in this situation as none of these makes react-router to call getComponent on that route.

This sounds like your responsibility, not ours. We don't prescribe a way for you to load your data.

The responsibility of users is to fetch its own data.

The responsibility of react-router is the rendering of routes, but also providing a solution when a user needs a route to be rendered from scratch without implementing tedious and ugly hacks.

There could be 2,000 people asking for this feature to try to reload their data, but React (not React Router) just doesn't work that way.

I think if 2,000 people (which is a major number considering the size of whole community.) ask for something, react, react-router or whatever it is should change itself to satisfy their needs too.
Meanwhile I solved the problem for myself and I described my solution here: https://github.com/reactjs/react-router/issues/3770#issuecomment-243493806

It only takes one PR to implement a feature.

Come on @taion , the problem is not the pr. Honestly I'd stop my work and spend 3 working days if required to solve this and provide a pr if I was sure core developers accept it.

This sounds like your responsibility, not ours. We don't prescribe a way for you to load your data.

Core developers think it's not react-router's responsibility and they think even if 2000 people ask for it "it's not the way react works." no matter how many people need it and no matter how much trouble it introduces for them, we just don't care period, new line.

No one expects core developers of a repository on github to spend their time implementing features community needs. Everyone is thankful for every second a developer works on an open source project even if he's paid for it. But on the other side no one likes being ignored or being told he should change the requirements of his project to make it compatible to the library or he should just do things the way core developers like following their paradigms. Closing issues without explanation or explanations like "it's just not how it works" or "It's not our responsibility" is just like ignoring people's problems.

It only takes one PR to implement a feature

And a repo owner to accept the PR.

But on the other side no one likes being ignored or being told he should change the requirements of his project to make it compatible to the library or he should just do things the way core developers like following their paradigms. Closing issues without explanation or explanations like "it's just not how it works" or "It's not our responsibility" is just like ignoring people's problems.

Indeed.

react-router is one of the reasons we are ditching React for Vue. We are tired of having to implement convoluted solutions for stuff that should be really simple or having to deal with the Soviet Politburo.

seriously ReactDOM.unmountComponentAtNode(node) works, why do you guys need more?

OK, just to clarify some things here before this totally gets out of hand: This already exists in React. You can call this.forceUpdate() to achieve the same results. It doesn't makes sense for the Router to try and duplicate a function of the underlying framework.

Our behavior of not re-creating an element if the URL params change is related here. That is founded in basic React behavior: If the render function of a component provides the same set of elements back but with different props, this will not create a new instances, but will call componentWillRecieveProps on the existing instances instead. This is all idiomatic React behavior.

To change or wrap this behavior in the library doesn't really make sense. It's built-in. No PR is needed here.

seriously ReactDOM.unmountComponentAtNode(node) works, why do you guys need more?

Doesn't work server-side, which is why forceUpdate() would be better.

You can call this.forceUpdate() to achieve the same results. It doesn't makes sense for the Router to try and duplicate a function of the underlying framework.

What if you want to refresh from outside the component?

The easiest way to rerun the route matching and "reload" the route/page is to just call:

router.replace(location);

Pull the router and/or location from context or a route component as appropriate.

You can alternatively unmount and remount the <Router> instance, I think replacing the current history location is the easiest way to do this.

I think replacing the current history location is the easiest way to do this.

Could you please let me know how? A code sample or a reference in docs would help.

Pull the router and/or location from context or a route component as appropriate.

Again, how do you do that outside of a component without context, withRouter(), or the <Route>?

Again, how do you do that outside of a component without context, withRouter(), or the ?

I guess "You can alternatively unmount and remount the instance, I think replacing the current history location is the easiest way to do this." is the solution but I don't know how we can do this.

I guess "You can alternatively unmount and remount the instance, I think replacing the current history location is the easiest way to do this." is the solution but I don't know how we can do this.

If that is the "best" solution it's clear why react-router needs a method to refresh the current route.

Outside the router component is history.replace(location).

Note that forceUpdate actually won't work.

Outside the router component is history.replace(location).

history as in window.history?

No, as in the history object you pass into the router.

More broadly, the router itself is, in this context, just a React component. If you want to fully re-render it, then just do so! Change the key, unmount-then-remount, whatever.

What you seem to be asking for is a way to re-run the route matching, which is just replacing with the current location using the router context object or history object, but otherwise, as a React component, React already _gives you_ ways to remount it, and it's bad API design to have a separate API for something where the upstream already offers an API.

No, as in the history object you pass into the router.

So if the code that needs to refresh the router is outside of a component, say in MyClass, we need to pass a reference of that history object to MyClass, listen to history changes to be able to get the latest location, and implement a method in MyClass to be able to do history.replace(location).

Is that it?

The built-in history instances are singletons, so just import { browserHistory } from 'react-router' anywhere to have access to the same instance.

It's impossible to say what the best approach here is without going into the details of your use case. The Redux integrations give you a number of ways the accomplish this. Otherwise, you're invariably going to need to save off _some_ reference to _something_.

How you choose to abstract these concepts is out-of-scope here. Any sort of API or instance method on the router object itself actually presents the same challenges as just remounting the component (e.g. by changing the key), as you need to get a reference to the router object.

You're free to use other media to complain. However, we ask that we focus the issue tracker on discussing bugs and feature requests. You're also free to make PRs, to the extent that those PRs integrate with the existing API and that the features added are sufficiently valuable to pay for the increased API surface area, which creates a burden on all users of this library.

@timdorr

The built-in history instances are singletons, so just import { browserHistory } from 'react-router' anywhere to have access to the same instance.

That doesn't work since you first need to figure out if you passed browserHistory or hashHistory to the router.

@taion

You're free to use other media to complain. However, we ask that we focus the issue tracker on discussing bugs and feature requests

I thought we were discussing the feature request of implementing a refresh method to the router. I'm presenting a use case were such method would solve the problem without doing acrobatics but if you think what I'm doing is simply complaining then it's clear what the problem really is.

Don't worry, I won't complaint anymore.

Take care.

That doesn't work since you first need to figure out if you passed browserHistory or hashHistory to the router.

Then create your own singleton.

@timdorr @taion thanks for help guys, browserHistory.replace(location) did the job, now I can get rid of my reRender action.

Btw, I strongly believe this issue should've been open till this very moment.

For those who use react-router-redux, you can dispatch a replace action, you can import the replace action from react-router-redux.

@sassanh
I tried with react-router-redux

When app is on /posts url, I signin via modal window then I want the components to refresh

dispatch(replace('/posts'))

but it does not refresh the component, so for now I resort to a similar technique of yours but not convinced

dispatch(replace('/loading'))
dispatch(replace('/posts'))

@rohandey What do you expect from a refresh? What I expected as described above was to call getComponent of the Route and it does it, I could handle it only with a utility in react-router as it was related to routes and couldn't be solved with bare react solutions.

Can you tell us what do you mean by a refresh? Do you want the render method of your component to get executed or your component to be mounted again (componentDidMount gets called) ? or do you want the componentDidUpdate to get called? Each has its own solution. First needs forceUpdate, second can be achieved by

dispatch(replace('/loading'))
dispatch(replace('/posts'))

as you mentioned, though I guess you should consider reviewing your app's logic if it needs it. (there are other dirty methods to achieve this without changing location, the most straight forward one is to manually unmount and remount the component, or if it's a child component, you can replace it with null and then bring it back. But I don't know a clean method for it and don't know a use-case for it neither.) The third can be done by calling setState and changing a dummy variable in state. (I'd try to design the logic of the app in a way that doesn't need such things though.)

@sassanh On url /posts we have many widgets like latest posts, top posts. After signin we want to refresh all the content, one way is to dispatch various actions which would refresh these widgets.
But similar to /posts we have other urls /dashboard etc which again have many widgets and after signin from modal window we want to close the modal and refresh all the content.

In the code below in else part we currently replace url which eventually calls various dispatch actions for that url and refreshes data via ajax

const mapDispatchToProps = (dispatch) => {
  return {

    signin_user: (data, location_path) => {
      dispatch(signin_user(data)).then((response) => {
        if(response.error){
          dispatch(signin_user_failure(response.payload));
        }else{
          //in real apps auth token to be set in place of user id
          localStorage.setItem('auth_token', response.payload.data.token);
          dispatch(signin_user_success(response.payload));
          dispatch(replace('/loading'))
          dispatch(replace(location_path))
        }

      });
    }

  }
}

Other way we can think of is check the current location_path and on case basis dispatch various actions

switch(location_path) {

    case '/posts':
      dispatch(latest_posts());
      dispatch(tops_posts());

    case '/dashboard':
      dispatch(my_posts());
      dispatch(my_tops_posts());

   } );

But this would mean for every url we have to create a new case and can become error prone in case we forget to list all dispatch actions.

I had same situation for my app too (re-rendering things with new data after user signs in.) You can have a prop/some props that goes through all components hierarchy which have different values if user is signed in or not. A single action changes the value of these props (and the change goes through all hierarchy.) and components handle it either implicitly or explicitly via componentDidUpdate (if for example the widget needs to call a rest endpoint and get new data.)

I think the nature of the change you're talking about (changing what renders after user signs in.) should be handled via changing props when user logs in. To summarize you can make widgets update themselves via a prop that goes through hierarchy instead of dispatching actions to them. Generally I found it cleaner to let the communication from parent to children happen via props and communication in other direction happen via actions.

I just had a similar requirements. Why not simply add a method reload to the router which basically does something like @props.router.replace(window.location).

Seems quite demanded:
https://github.com/ReactTraining/react-router/issues/2097
https://github.com/ReactTraining/react-router/issues/3545
...

@pauldotknopf I have the same issue here. Have any solution yet?

@dktan, Nope.

@pauldotknopf I tried @rohandey 's solution, replace to a empty loading page and replace back, works for now...

@dktan Only problem to this solution is if someone clicks browser back button, it will show them a loading page.

@rohandey if you use replace it shouldn't, only push leaves a history according to doc.

@sassanh can you please explain how you solved using browserHistory.replace(location)?
I put browserHistory.replace(location) in the onClick handler but I'm not sure where to put the fetch call. In the routing onEnter handler?

@uccio83 Sorry I'm not in the context. what fetch call? Can you explain your exact problem after using browserHistory.replace(location)?

@sassanh You talked formerly about a fetch call, so I hooked to your topic. Sorry but I don't have the time to further explain the context. I solved in another ugly way in the onclick handler: if the clicked route is different from the current route I update the routing using browserHistory.replace(location), else I force the update of the component dispatching a fetch request action.
The problem remains that the react router developers won't support the "same route update". Thanks anyway for your reply

@uccio83 I searched this page for word "fetch" and didn't find any comment by me that contains word "fetch". I'm glad you solved your issue and sorry you solved it in an ugly way. I'm willing to help if you got the time to explain more :-)

@ryanflorence

There could be 2,000 people asking for this feature to try to reload their data, but React (not React Router) just doesn't work that way.

Nope, you're wrong.
Not unmounting a component is a broken behaviour of react-router.
Handling state mutation is exactly what React was meant to avoid so react-router is contradicting the whole React philosophy by doing such a lame thing (not remounting routes).
Did you even read React documentation and philosophy notes?

@taion

The easiest way to rerun the route matching and "reload" the route/page is to just call:
router.replace(location);

Yeah, and this way no browser history for a user, so that's not a solution

@timdorr

seriously ReactDOM.unmountComponentAtNode(node) works, why do you guys need more?

You bragged about it two times subsequently yet failed to provide a simplest example of how it might even work.

Okay, everybody, I managed to find a fix for this bug.
See the full commit for reference:
https://github.com/halt-hammerzeit/react-isomorphic-render/commit/d3d7f3aa0f771a8b1fb22c3c5324e3f249a01d15

The idea (not the actual code):

<Router createElement={ (component, props) =>
{
  const { location } = props
  const key = `${location.pathname}${location.search}`
  props = { ...props, key }
  return React.createElement(component, props)
} }/>

@halt-hammerzeit
Could you explain why the route component added key it will force remount when the router tried to mount? thanks.

@sevenLee When a URL changes then the key changes and different keys force React component remount

@halt-hammerzeit thanks, the idea really works for me :)

@halt-hammerzeit
if use this way, I found it will remount when location change every time. But I use query string in the same url path after submission. e.g.
/portal/reportByPlayer => /portal/reportByPlayer?gameKey=12345
I don't want to remount when this scenario, that means I need to check if the location path if they are the same, if the same, it should not give the new key on component. But react-router seem didn't give previous path in location property...do you have any idea?

Give a concrete step-by-step description

On Wed 8 Mar 2017 at 11:23 seven notifications@github.com wrote:

@halt-hammerzeit https://github.com/halt-hammerzeit
if use this way, I found it will remount when location change any time.
But I use query string in the same url path after submission. e.g.
'/portal/reportByPlayer" => "/portal/reportByPlayer?gameKey=12345', I don't
need to remount when this scenario, that means I need to check if the
location path if they are the same, if the same, it should not give the new
key on component. But react-router seem didn't give previous path in
location property...do you have any idea?


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/ReactTraining/react-router/issues/1982#issuecomment-284977187,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAdH77WaHjUo4x5eqaC96ocv1v0Mbk7jks5rjmVmgaJpZM4F-lat
.

@halt-hammerzeit
Sorry for the previous question. I have another question encountered, below:
scenario: I am in /portal/reportByPlayer, it handled by ReportByPlayer.jsx. I would like to click h3 in ReportByPlayer component for remounting ReportByPlayer component. because in FormByPlayerCT(ReportByPlayer's children), it fetch datas in componentDidMount, that's why I need to remount ReportByPlayer, that will trigger fetch datas in componentDidMount. However, the route path is the same, the key is the same, too, it means it won't remount any more. How I trigger remount in this situation? Thanks !

Routes.jsx

export default (
    <div>
        <Route path="/" component={Login} />
        <Route path="login" component={Login} />
        <Route path="portal" component={Layout} >
            <IndexRoute component={Welcome}></IndexRoute>
            <Route path="reportByPlayer" component={ReportByPlayer}></Route>            
        </Route>
    </div>
)

Root.jsx

class Root extends Component {
    render() {
        const { store } = this.props
        const history = syncHistoryWithStore(browserHistory, store)
        return (
            <Provider store={store}>
                    <Router
                        history={history}
                        routes={Routes}
                        createElement = { (component, props) => {
                            const { location } = props
                            const key = `${location.pathname}${location.search}`
                            props = { ...props, key }
                            return React.createElement(component, props)
                        }}
                    />
            </Provider>
        )
    }
}

ReportByPlayer.jsx

class ReportByPlayer extends Component {
    render() {
        const  { resetDisplayStates } = this.props

        return (
            <ContentWrapper>
                <h3>
                    <a onClick={() => {
                        browserHistory.replace(`/portal/reportByPlayer`)
                    }}>P&L Report by Player</a>
                </h3>                
                <FormByPlayerCT />
            </ContentWrapper>
        )
    }
}

So you basically want to refresh the page.
No need to remount then, just refresh the data manually (via a redux action or something).

@halt-hammerzeit
I use browserHistory.replace('/portal/reportByPlayer'), but it doesn't trigger componentDidMount in FormByPlayerCT. I think the main reason is the key is the same (it comes from route, because route is the same). I don't want reload page, I just want remount ReportByPlayer component then trigger componentDidMount of FormByPlayerCT.

Then you can trigger whatever is inside componentDidMount manually like this.refs. FormByPlayerCT.load()

Where did FormByPlayerCT.load() come from? I didn't have this function on FormByPlayerCT component...

Move the contents of componentDidMount to load

@halt-hammerzeit
Sorry, I still can't get it..if I call browserHistory.replace('/portal/reportByPlayer') on ReportByPlayer, that will re-render ReportByPlayer and FormByPlayerCT. I put manually load function in ReportByPlayer render....but Where I can put this.formPlayerCT.load in ReportByPlayer?

ReportByPlayer.jsx

class ReportByPlayer extends Component {
    render() {

        return (
            <ContentWrapper>
                <h3>
                    <a onClick={() => {
                        browserHistory.replace(`/portal/reportByPlayer`)
                    }}>P&L Report by Player</a>
                </h3>                
                <FormByPlayerCT ref={(comp) => this.formPlayerCT = comp} />
            </ContentWrapper>
        )
    }
}

if I call browserHistory.replace('/portal/reportByPlayer') on ReportByPlayer, that will re-render ReportByPlayer and FormByPlayerCT

You don't need rerendering. You said what you needed was remounting. Remounting can't be done while preserving the same URL, so the only option is to move loading code somewhere out of componentDidMount

if I call browserHistory.replace('/portal/reportByPlayer') on ReportByPlayer, that will re-render ReportByPlayer and FormByPlayerCT

You don't need rerendering. You said what you needed was remounting. Remounting can't be done while preserving the same URL, so the only option is to move loading code somewhere out of componentDidMount and call it manually

@halt-hammerzeit
Thanks! I got it now, but I don't know where place can put loading code in ReportByPlayer to replace child component componentDidMount place, I can't put this.formPlayerCT.load in ReportByPlayer render function....could you teach me the actual implementation? or suggestion, thanks again!

Actually I don't just want to load data, I need to remount whole ReportByPlayer component. If I only call this.formPlayerCT.load, it is not enough. How I do remounting under the same URL, then trigger child to fetch data with the key remounting mechanism?

I worked around use browserHistory.replace('/portal/reportByPlayer?key=${Math.random()}'), that make me remount component in the same route, but not actually the same path....

Oh, that's a viable solution

On Tue 14 Mar 2017 at 10:50 seven notifications@github.com wrote:

I worked around use
browserHistory.replace('/portal/reportByPlayer?key=${Math.random()}'),
that make me remount component in the same route, but not actually the same
path....


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/ReactTraining/react-router/issues/1982#issuecomment-286346237,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAdH7xbZXPndco5h36_KFBxpl4--cp9qks5rlkbDgaJpZM4F-lat
.

I'm using v3 of the API.

None of the above solutions seem to work. I don't want to pollute the URL with randomised data in order to prompt a refresh.

How should we trigger the onEnter callbacks to re-run for the current route?

The use case I have is to re-run the onEnter logic for the current route after the user logs in using an inline login box. Some pages it will be OK for the user to remain where they are, but, for example, should be redirected to a default route if they are on a registration page - since the registration page is only intended for non-authenticated users.

Chris

I'm using v3 of the API.
None of the above solutions seem to work.

My solution works on v3

@halt-hammerzeit - I would like a non random path generation solution. I was referring to the above-mentioned refresh methods, which don't seem to be on the context router object.

Well, I came up with yet another idea for such cases:
Say, create a special <Redirector/> component in which inside componentDidMount() add history.replace(this.props.location.query.to).
Next create a <Route path="redirect" component={Redirector}/>.
After logging in a user via an inline login box redirect him (via history.replace()) to /redirect?to=/the/original/page.
When the user is redirected, the original page is unmounted, and the <Redirector/> page is mounted.
Immediately after it mounts it redirects the user back to the original page which is mounted anew.

My solution works on v3

What about v4?

What about v4?

v4 is still an immature library and lacks a lot of the features v3 had therefore I'm not using v4 and not planning to switch to it since v3 has everything one could need.

If you are using react-router you can use history.go(n). if n=0 then reload current page.

this.props.history.go(0)

You can access history import { withRouter } from 'react-router-dom' with this.

Solved the problem by creating "empty" route.

      <Router history={hashHistory}>
            <Route path="/">
                 ... my routes ...
                <Route path="empty" component={null} key="empty"/>
            </Route>
        </Router>

When I need refresh, I go to the empty route first, then setTimeout, and replace state to current route:

    if (routesEqual && forceRefresh) {
        router.push({ pathname: "/empty" });
        setTimeout(() => {
            router.replace({ pathname: route, query: query });
        });
    }

By making replace I keep my browser history clear from empty route. Using just "/" route is not good, because some "home" component will mount and make some unrequired logic. Empty route does nothing.

p.s. Hello to core developers, very nice from them. Spent 1-2 hours to this shit.

Mates. SetTimeout always makes me unsure. What about just to put timestamp in route definition:

<Route path="/asd" component={() => <Asd hash={Date.now()} />} />

And then reload it when it changes:

componentWillReceiveProps = (props) => {
        if(props.hash !== this.props.hash){
            console.log('route reload');
        }
    }

BUT of course - there can't be connect function or other props - because it may cause issues with useless rerendering.

Solution for v4 (I am still playing with React/Redux, so I am not yet aware of some potential breaking):

<RouteChanged>
  <Switch>
    <Route
      exact path={RoutePaths.home}
      component={LoginRequired(Entries)}/>

And then in my RouteChanged:

class _RouteChanged extends React.Component {
    ...

    render() {
        const childrenWithProps = React.Children.map(this.props.children,
            (child) => React.cloneElement(child, {
                key: this.props.location.key
            })
        );
        return (
            <div>{childrenWithProps}</div>
        )
    }
...
}

@PFight A awesome solution! Think you very much! ∩_∩

We had a similiar challenge while developing a quite complex app. We needed to reload a complex view (and reset it to its initial state) when its corresponding <Route> was triggered (by a <Link>) no matter whether it was already active. Whenever the route was triggered, linked view should reload.

The solution I came up with is very simple and does not play with routes, only with components. I call it reloadable components. It's a simple HOC that makes the wrapped component kind of aware of the app's location (location property must be passed to it somehow, <Route> component does this automatically) so that when browser location does not change but location's key property changes the component is reloaded. Location's key property changes when a route is requested/triggered, even when that route has not changed. That's the behavior I'm exploiting to "reload" a component which in essence implies unmounting a component and mounting it again right afterwards.

The key property in location object is managed by history package since v1.12.1 onwards. Because react-router depends on history package since v1.x I think it's quite safe to say this approach will work for most cases.

Note that since react-router depends on history package you cannot use createHashHistory since it does not support location.key.

Another benefit of this approach is you can reload just the components you want to.

Without further ado, here you're the HOC:

import PropTypes from 'prop-types'
import React, { Component } from 'react'

const reloadable = WrappedComponent => {
  class Reloadable extends Component {
    constructor (props) {
      super(props)
      this.state = {
        reload: false
      }
    }

    componentWillReceiveProps (nextProps) {
      const { location } = this.props
      const { location: nextLocation } = nextProps

      if (nextLocation.pathname === location.pathname &&
          nextLocation.search === location.search &&
          nextLocation.hash === location.hash &&
          nextLocation.key !== location.key) {
        this.setState({ reload: true }, () => this.setState({ reload: false }))
      }
    }

    render () {
      return this.state.reload ? null : <WrappedComponent {...this.props} />
    }
  }

  Reloadable.propTypes = {
    location: PropTypes.object
  }

  return Reloadable
}

export default reloadable

How to use it

// This is pretty much pseudo-code so you get the idea

// Connect a route so it renders ComponentA reloadable component
// Trigger this route to load ComponentA for the first time.
// Afterwards, trigger this same route again to reload ComponentA: A different random number should be displayed.
<Route path='/test' component={ComponentA} />

// Content of ComponentA.js
import React from 'react'
import reloadable from '[path-to]/reloadable'
export default reloadable(() => <p>`A random number: ${Math.random()}`</p>)

How to reload only some child components linked to a Route?

This scenario assumes a <Route> that renders a non-reloadable parent component composed of some reloadable child components and you want to reload only those reloadable child components.
To solve this, pass location property down from the non-reloadable parent component to those reloadable child components.

It's working great for us in our app. Hope it works fine for yours too ;)

@tubalmartin as a follow up and to kind of state the obvious if any of these properties in the react-router library are changed this will probably break... I've been looking for a good way to do this, and so far this looks like it may work best as a general solution.

      if (nextLocation.pathname === location.pathname &&
          nextLocation.search === location.search &&
          nextLocation.hash === location.hash &&
          nextLocation.key !== location.key) {

We found a way around it using a two different ways

In our router we have

<Route
   path="/u/:handle"
   pageName="Profile"
   pageDescription="Manage your profile"
   getComponent={(location, cb) => { System.import('./Profile').then((component) => { cb(null, component.default || component); }); }}
/>

to view your own profile it calls:
/u/myprofile

to call another users profile it is:
/u/usershandle

we had an issue where if you are in /u/usershandle and you click on “My Profile” /u/myprofile it wouldn’t change the page content. Vice versa.

Dirty Old skool method for the pages we don’t mind doing a full reload we replaced the 'Link to' tag with a standard 'a href' pointing to the url.
<a className={itemClass} href={this.props.link}>
This works fine.

Another method where we wanted only part of the page reloaded we shimmied it by using reducers.
On the actual page we add:

componentWillReceiveProps(nextProps) {
    if (nextProps.general && nextProps.general.reload === ‘profile’) {
      this.reloadProfile(); // call a function that does all needed to be done
    }  
}

On the menu item instead of using Link with ‘to’ (can also use a href or a button) we use onClick which calls a function that compares the url with location > pathname in the props.

if this.props.location.pathname === this.props.link (/u/myprofile etc)
call reducer:
this.props.setGeneral({ prop: 'reload', value: 'profile' });
once the componentWillReceiveProps catches the request it calls the function that does what we want to reload.

There is prob better ways to do this but this is the only way got it to work constantly across all platforms and browsers.

I just did it by setting target="_self" on the link tag

try this its work fine :) :+1:

constructor(route:ActivatedRoute) {
route.params.subscribe(val => {
// put the code from ngOnInit here
});
}

Was this page helpful?
0 / 5 - 0 ratings

Related issues

stnwk picture stnwk  ·  3Comments

andrewpillar picture andrewpillar  ·  3Comments

ArthurRougier picture ArthurRougier  ·  3Comments

alexyaseen picture alexyaseen  ·  3Comments

jzimmek picture jzimmek  ·  3Comments