Closed pdeva closed 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.
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 key
s 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.
How do i tell react-router to reload current route?