-
Notifications
You must be signed in to change notification settings - Fork 642
Prevent infinite loops when state is updated before the actual history change is fired #30
Conversation
I'm curious; you are dispatching an UPDATE_PATH action in I only ask because I want to help people do it the way that react-router is built for, and sometimes it's not obvious what the correct way is. What would happen if you did a |
I'm not sending any UPDATE_PATH action during a transition. Here's what happens in short:
|
I updated the solution to also consider the case when using the browser navigation buttons (which were creating the same issue). This solution is even simpler, we simply check if there was any update in the routing path every time there is a state update. |
Oh wow, that's really cool. I actually was not aware of |
Ok, perfect. FYI I actually took your fork of |
Cool! Did you put it somewhere? I can pick up where you left off. Been meaning to polish it off. |
I've got all code integrated in a project at the moment, need to find some time to put everything back into your |
@@ -50,7 +51,7 @@ function syncReduxAndRouter(history, store, selectRouterState = SELECT_STATE) { | |||
// Avoid dispatching an action if the store is already up-to-date, | |||
// even if `history` wouldn't do anything if the location is the same | |||
if(getRouterState().path !== locationToString(location)) { | |||
store.dispatch(updatePath(locationToString(location))); | |||
store.dispatch(updatePath(locationToString(location), true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you pass true
here? This flag disables all future router updates (only the redux state will change). It doesn't just disable it once.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When history.listen
is called the transition to the new route already happened, so a new pushState
is redundant. We only need an update to the state. Route changes triggered externally through actions/reducer will reset the flag (unless set explicitly in the action)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Won't getRouterState().path
and locationToString(location)
be equal here then if the transition already happened? What's the difference between this workflow and the normal one where you update the store which triggers a router update? The only difference is that this history.listen
event will be delayed, but it should end up in the same place: the values are equal here.
Just trying to fully understand what I'm merging in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The idea is that you keep the state up-to-date by listening to the history (lines 49-55), the history update might come from a direct call to history.pushState() or from hitting the back button of the browser. This causes the saved routing state (state.routing
) to be out of sync with the location. The purpose here is to keep the state updated as soon as we detect a transition.
The other way around is when we detect a change in state and we need to trigger a transition (lines 58-71), in this case somebody explicitly requested a route change by dispatching the UPDATE_PATH action and we need to react by calling history.pushState().
Please let me know if that makes sense
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, but that sounds like exactly how it has always worked. What is different now that noRouterUpdate
is needed?
When the store listener is called, the paths will be the same already and nothing will happen. (I am traveling today so I won't be too responsive.)
Can you possibly remove this true
, run it locally and tell me exactly what breaks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are actually right, it should't change anything because of the check currentState != currentLocation
, that true
doesn't add anything in terms of functionality. I added it for clarity, to make it clear that the action is not meant to update the route, but just the state. I'll remove that true
if you think is redundant.
OK, I added the comment. |
So, as promised I updated your |
Oh sweet, thanks for the PR! |
…istory change is fired
Thanks. I may even remove the |
Prevent infinite loops when state is updated before the actual history change is fired
@mariocasciaro Actually, you may be right about using the I'm going to open a new issue soon about refactoring that code. |
I dispatch some actions to redux during the
history.listenBefore
hook, this causes multiple state changes beforewindow.location
is actually updated and beforehistory.listen
is fired. This causes an infinite loop, becauseredux-simple-router
recognizes a route change at every state change, sincewindow.location
is still not updated, which in turn triggers thehistory.listenBefore
hook, which triggers new state changes, and so on. This PR fixes this issue by making sure not to re-invokepushState
unless the previous route transition is completed.