remix-run / react-router

Declarative routing for React
https://reactrouter.com
MIT License
53.1k stars 10.31k 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?

timdorr commented 8 years ago

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

taion commented 8 years ago

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.

PierBover commented 8 years ago

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

timdorr commented 8 years ago

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.

sassanh commented 8 years ago

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

sassanh commented 8 years ago

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

rohandey commented 8 years ago

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

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

rohandey commented 8 years ago

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

sassanh commented 8 years ago

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.

gucki commented 8 years ago

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

dktan commented 8 years ago

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

pauldotknopf commented 8 years ago

@dktan, Nope.

dktan commented 8 years ago

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

rohandey commented 8 years ago

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

dktan commented 8 years ago

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

federico-bellucci commented 7 years ago

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

sassanh commented 7 years ago

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

federico-bellucci commented 7 years ago

@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

sassanh commented 7 years ago

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

catamphetamine commented 7 years ago

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

catamphetamine commented 7 years ago

@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

catamphetamine commented 7 years ago

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

catamphetamine commented 7 years ago

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)
} }/>
sevenLee commented 7 years ago

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

catamphetamine commented 7 years ago

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

sevenLee commented 7 years ago

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

sevenLee commented 7 years ago

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

catamphetamine commented 7 years ago

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 .

sevenLee commented 7 years ago

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

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

sevenLee commented 7 years ago

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

catamphetamine commented 7 years ago

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

sevenLee commented 7 years ago

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

catamphetamine commented 7 years ago

Move the contents of componentDidMount to load

sevenLee commented 7 years ago

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

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

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

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

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

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

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 .

cjpete commented 7 years ago

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

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

My solution works on v3

cjpete commented 7 years ago

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

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

My solution works on v3

What about v4?

catamphetamine commented 7 years ago

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.

emeentag commented 7 years ago

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

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.