remix-run / react-router

Declarative routing for React
https://reactrouter.com
MIT License
52.79k stars 10.23k forks source link

how to reload a route? #1982

Closed pdeva closed 8 years ago

pdeva commented 9 years ago

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

andrefarzat commented 9 years ago

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

mjackson commented 9 years ago

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 commented 9 years ago

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 commented 9 years ago

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 commented 8 years ago

@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 commented 8 years ago
  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 commented 8 years ago

@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 commented 8 years ago

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 commented 8 years ago

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.

ryanflorence commented 8 years ago

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 commented 8 years ago

@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 commented 8 years ago

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 commented 8 years ago

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

cotwitch commented 8 years ago

@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 commented 8 years ago

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

reverofevil commented 8 years ago

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 commented 8 years ago

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

pauldotknopf commented 8 years ago

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 commented 8 years ago
<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:

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 commented 8 years ago

Use onChange

taion commented 8 years ago

It's new as of 2.1.0.

Kosmin commented 8 years ago

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 commented 8 years ago

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:

nvartolomei commented 8 years ago

@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 commented 8 years ago

@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 commented 8 years ago

@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

Kosmin commented 8 years ago

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)

davis commented 8 years ago

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

shprink commented 8 years ago

You guys can use unmountComponentAtNode

ReactDOM.unmountComponentAtNode(node)
sassanh commented 8 years ago

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));
sassanh commented 8 years ago

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.

PierBover commented 8 years ago

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.

sassanh commented 8 years ago

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

taion commented 8 years ago

It only takes one PR to implement a feature.

sassanh commented 8 years ago

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.

PierBover commented 8 years ago

It only takes one PR to implement a feature

And a repo owner to accept the PR.

PierBover commented 8 years ago

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.

shprink commented 8 years ago

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

timdorr commented 8 years ago

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.

timdorr commented 8 years ago

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

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

PierBover commented 8 years ago

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?

taion commented 8 years ago

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.

sassanh commented 8 years ago

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.

PierBover commented 8 years ago

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

sassanh commented 8 years ago

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.

PierBover commented 8 years ago

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.

taion commented 8 years ago

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

Note that forceUpdate actually won't work.

PierBover commented 8 years ago

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

history as in window.history?

taion commented 8 years ago

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.

PierBover commented 8 years ago

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?