New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

how to reload a route? #1982

Closed
pdeva opened this Issue Sep 16, 2015 · 108 comments

Comments

@pdeva

pdeva commented Sep 16, 2015

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

@andrefarzat

This comment has been minimized.

andrefarzat commented Sep 16, 2015

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

@mjackson

This comment has been minimized.

Member

mjackson commented Sep 20, 2015

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

@pdeva

This comment has been minimized.

pdeva commented Sep 20, 2015

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

@mjackson

This comment has been minimized.

Member

mjackson commented Sep 20, 2015

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.

@icem

This comment has been minimized.

icem commented Jan 15, 2016

@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');
@ryanflorence

This comment has been minimized.

Contributor

ryanflorence commented Jan 16, 2016

  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.

@icem

This comment has been minimized.

icem commented Jan 16, 2016

@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

@icem

This comment has been minimized.

icem commented Jan 16, 2016

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.

@taion

This comment has been minimized.

Contributor

taion commented Jan 16, 2016

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.

@taion taion reopened this Jan 16, 2016

@taion taion closed this Jan 16, 2016

@ryanflorence

This comment has been minimized.

Contributor

ryanflorence commented Jan 16, 2016

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.

@icem

This comment has been minimized.

icem commented Jan 16, 2016

@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.

@mezod

This comment has been minimized.

mezod commented Mar 1, 2016

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.

@jeffpc1993

This comment has been minimized.

jeffpc1993 commented Mar 2, 2016

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

@cotwitch

This comment has been minimized.

cotwitch commented Mar 6, 2016

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

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

@jeffpc1993

This comment has been minimized.

jeffpc1993 commented Mar 6, 2016

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

@polkovnikov-ph

This comment has been minimized.

Contributor

polkovnikov-ph commented Mar 22, 2016

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.

@pavelivanov

This comment has been minimized.

pavelivanov commented Apr 5, 2016

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

@pauldotknopf

This comment has been minimized.

pauldotknopf commented Apr 6, 2016

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>
  );
};
@Kosmin

This comment has been minimized.

Kosmin commented Apr 21, 2016

<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

@taion

This comment has been minimized.

Contributor

taion commented Apr 21, 2016

Use onChange

@taion

This comment has been minimized.

Contributor

taion commented Apr 21, 2016

It's new as of 2.1.0.

@Kosmin

This comment has been minimized.

Kosmin commented Apr 21, 2016

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

@Kosmin

This comment has been minimized.

Kosmin commented Apr 21, 2016

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 💥

@nvartolomei

This comment has been minimized.

Contributor

nvartolomei commented May 11, 2016

@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.

@Kosmin

This comment has been minimized.

Kosmin commented Jul 6, 2016

@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

@davis

This comment has been minimized.

davis commented Jul 20, 2016

@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

@sevenLee

This comment has been minimized.

sevenLee commented Mar 13, 2017

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

@catamphetamine

This comment has been minimized.

catamphetamine commented Mar 13, 2017

Move the contents of componentDidMount to load

@sevenLee

This comment has been minimized.

sevenLee commented Mar 14, 2017

@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>
        )
    }
}
@catamphetamine

This comment has been minimized.

catamphetamine commented Mar 14, 2017

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

@catamphetamine

This comment has been minimized.

catamphetamine commented Mar 14, 2017

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

@sevenLee

This comment has been minimized.

sevenLee commented Mar 14, 2017

@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!

@sevenLee

This comment has been minimized.

sevenLee commented Mar 14, 2017

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?

@sevenLee

This comment has been minimized.

sevenLee commented Mar 14, 2017

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

@catamphetamine

This comment has been minimized.

catamphetamine commented Mar 14, 2017

@cch5ng cch5ng referenced this issue Mar 23, 2017

Closed

add search/filter function #39

4 of 6 tasks complete
@cjpete

This comment has been minimized.

cjpete commented Apr 11, 2017

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

@catamphetamine

This comment has been minimized.

catamphetamine commented Apr 11, 2017

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

My solution works on v3

@cjpete

This comment has been minimized.

cjpete commented Apr 11, 2017

@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.

@catamphetamine

This comment has been minimized.

catamphetamine commented Apr 11, 2017

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.

@a-x-

This comment has been minimized.

a-x- commented May 13, 2017

My solution works on v3

What about v4?

@catamphetamine

This comment has been minimized.

catamphetamine commented May 13, 2017

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.

@hopbalabi

This comment has been minimized.

hopbalabi commented Jun 2, 2017

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.

@PFight

This comment has been minimized.

PFight commented Jul 10, 2017

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.

@damianprzygodzki

This comment has been minimized.

damianprzygodzki commented Jul 28, 2017

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.

@borisozegovic

This comment has been minimized.

borisozegovic commented Aug 3, 2017

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>
        )
    }
...
}
@CodeLittlePrince

This comment has been minimized.

CodeLittlePrince commented Aug 7, 2017

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

@tubalmartin

This comment has been minimized.

tubalmartin commented Aug 8, 2017

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

@cclifford3

This comment has been minimized.

cclifford3 commented Aug 17, 2017

@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) {
@raynoppe

This comment has been minimized.

raynoppe commented Oct 19, 2017

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.

@michaelolo24

This comment has been minimized.

michaelolo24 commented Nov 15, 2017

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

@kr1304

This comment has been minimized.

kr1304 commented Nov 15, 2017

try this its work fine :) 👍

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

@ReactTraining ReactTraining locked and limited conversation to collaborators Nov 15, 2017

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.