react-router: component from react-router-dom misbehaves on cancelling navigation
I’ve recently migrated from version 3 to 4.1.2 in the hope that the following bug caused by the setRouteLeaveHook won’t be present in the <Prompt /> component from react-router-dom.
So, as per the use case of Prompt component, when the user standing in the form refreshes the page, edit some fields and clicks back button, the confirmation alert pops up and when the user cancels the navigation, it stays on the page but the url gets changed and does not remain the same as of the current form page.
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 82
- Comments: 64 (16 by maintainers)
I’m experiencing the exact same thing using
BrowserRouter. If the user clicks a link or does an operation that pushes a route to history everything works as expected. If however the user clicks the browser back button I get a prompt as expected but the URL in the address bar has changed to the previous path.If I then click cancel on the prompt indicating I do not wish to leave, one of two things will happen.
In both cases my current component is still shown and my components lifecycle hooks including
componentWillReceivePropsare called which messes up the state of my component.I experienced the same issue.
When I come from an in app route, the behaviour of Prompt is working as expected, but when I access the page directly with the url of the view containing the Prompt component and I trigger it, the browser url remains modified even if I click on cancel.
I made a repo to reproduce the issue : https://github.com/Edistra/react-router-dom-prompt-issue
I also made a gif of the issue :
In case this is helpful to anyone, I ended up writing my own version of the Prompt component which doesn’t have this bug and also prompts for leaving the page by closing the tab. Written in TypeScript and working with
history/createBrowserHistoryfromhistory@4.7.2,react-router-dom@4.3.1andreact-router-redux@5.0.0-alpha.8.I am having this very same issue, i have Formik implemented with react and the browser back button when clicked does throw the alert… but upon clicking cancel button the state of the form is lost… hence, doesnt make any sense if you prompt user for unsaved changes and just clean the form when they click cancel . 😕 Im surprised to see that this issue is open since 2017 , have tried all the upper mentioned tricks… none of them work for me…
It’s weird that this has been open open for 2 years now, but with no actual solution, on an actively maintained repo. Is there something preventing us from fixing it ?
Same problem on react-router-dom “^4.3.1” I am using… I use Prompt to alert user when leaving the current page wherein data/inputs provided are not yet submitted. Is there any fix this time? Thanks.
I’ve done a little more debugging and hopefully can shed some light on the unexpected behavior, though I still don’t have a solution. Testing the default
Promptfromreact-router-dom, I’m seeing the same behavior in both Firefox 69.0.1 and Chrome 77.0.3865.90.Let’s say I navigate from page A -> B -> C, where only page C has a
Prompton it.When I click the browser’s back button, the URL changes to B and the prompt comes up. If I cancel, the URL changes back to C and data is retained. If I instead click OK, the URL stays at B and data changes accordingly.
Here’s where the issues begin: Now, after navigating to page C, if I refresh the page, the browser still has pages A and B in its history. If I click the browser’s back button, the URL changes to B and I get the prompt, as expected. If I click OK, the URL stays at B and the data changes, also as expected. However, if I click cancel, the URL still stays at B, and data does not change to reflect the new URL.
FWIW the same also applies to the browser’s Forward button.
Basically I think the key to finding unexpected behavior is when react-router didn’t control the initial route navigation (e.g. when the URL was manually entered, the page was refreshed, or possibly from an
atag rather than aLink).Someone just needs to make a PR.
I have the same problem Look this animation.
Below this part of my code
Is there any hack for this to be solve in the mean time?
I made a gif of the issue I’m experiencing, which is rather strange behavior: https://gfycat.com/HotNeatInganue
Repro steps:
//brokenThe path change between steps 3 and 4 makes React think you’ve already navigated away before you even get a chance to select “Cancel” or “Ok” on the dialog box, so React unmounts the “Broken” component and then re-renders the “Broken” component after you click on “Cancel” which is when the path switches back. So if you’re usingjk, there was another issue that was causing the unmount<Prompt />on a page with a form, then you just lost all the data saved in the form component’s state due to the unmount.I’m running into this this issue as well. I’ve created a a test case: https://codesandbox.io/s/4lvjoow5mw. I get the same behavior on a real browser.
I am also facing similar kind of issue, for me I am using
Promptwithhistory.pushandbrowser back button. When I click on either of them, and then click onCancel, the url first gets changed. Then it gets back to previous url, but thestategets reset. Due to this, I lose my progress. Please help me to get through this.This is a browser related issue, not a issue in react-router or react itself. Not even related to JS. Since what we are trying to achieve is to handle a browser event (back button click), during this the state of page is lost as the history array is popped then pushed again.
You can actually see this by implementing a window listener for ‘onBeforeUnload’… u will see it changing in the console. But notice only for back button not window close button. Which is strange.
I also went a little deeper to see how WordPress handles this situation, on WordPress u can also see a kind of jerk or delay in re-fetch of data when u click back button, it seems like WordPress developers have gone out of the way to maintain a seperate trail of changes of the form area in the browser’s local storage.
So, its better to inform chrome, Firefox, etc about this so that they can patch it.
try this
usePrompthook https://twitter.com/sseraphini/status/1123321881942265856https://gist.github.com/sibelius/60a4e11da1f826b8d60dc3975a1ac805
I’ve been wrestling with the same issue, and finally found a solution that works for my use case. As others have pointed out, when a
POPaction triggers thegetUserConfirmationmethod and the navigation is canceled, React Router correctly prevents the route transition within the React app, but the browser has already popped that item fromwindow.history, so the URL in the browser will have changed.My solution was to update my
getUserConfirmationhandler to detect aPOPaction and callwindow.history.forward()to return to the original URL within the browser history. But in order forgetUserConfirmationto know what action was performed, I needed to pass along that information in the message from the<Prompt />component. The message is just a string, so I’m using a serialized JSON string to pass along the action along with my message and some other structured content that I need for my customized confirmation prompt.As a bit of context, I’m using the
HashRouterbecause my use case is within a browser extension. I can’t verify whether the same issue happens or whether my solution is adequate for theBrowserRouter. I also don’t know if this addresses all of the scenarios discussed in this thread, but I wanted to provide a reduced example of my solution in hopes that it can help someone out:@joaquinwojcik I have tried it and it does prevent me from a redirect whenever the condition is true. However the url is still updated to the previous one.
After a bit of tweaking, I have replaced the window history for now with the current path and it doesn’t change to the previous url.
This fixes it for me now. It also has a support for majority of the browsers. People reading this may check the browser support as per the project’s user base.
This reloads the component. If your a page has a form, the user inputs vanish
Ohai
I don’t know whether this helps anyone out, but I had the same issue: On back button I was prompted but the url indeed changed and sometimes it messed up the page sometimes it did not. (react-router/ -dom version 5.0.0)
After some debugging I realized that I have been using components from ‘react-router’ and ‘react-router-dom’ completely mixed, sometimes I import them from one sometimes the other. Now this should not matter much, but apparently in this case it does.
After I have ditched ‘react-router’ in favor of importing everything from ‘react-router-dom’ it works as ‘expected’ (Such as on browser back button it : 1. change the url, but does not unload the component, 2. shows the prompt window, 3. if I choose cancel the url is written back and the component is not unmounted or messed up).
I’m also using
import {Router, Route} from 'react-router-dom';with exported historyimport {createBrowserHistory} from "history"; export default createBrowserHistory();I seem to be observing similar behavior without the
<Prompt>component, but instead while trying to implement something like https://github.com/ReactTraining/react-router/issues/4635#issuecomment-300465164 (a custom styled popup in my app to handle the back button). The URL bar string changes before my custom popup renders, but the page acts as expected (my popup shows and the app doesn’t re-route). The difference is it always seems to change & stay changed after using the browser back button, rather than the reported cases here where the URL flips back after the dialog is closed.This is the only way I’ve found to prompt user every time he tries to exit and also keep the URL (it change to previous URL but if user press “cancel” it’ll change again to current url)
If I remove
whenprop from<Prompt>or even if I remove params fromcheckfunction, it’ll show the prompt only one time without keeping the URL.This issue is still present with react-router 5.1.2.
Reading through the thread, I can’t tell if the issue is with react router or the history package.
What is the best work-around so far, if any?
This bug is over a year old and one of the most upvoted issues here. It would be very nice to get an update on the status of this issue or some hack to fix this. Thanks!
Any update on this issue ? I’m facing the same problem
Any updates on this issue @timdorr ? I’m experiencing the same problem, using React Router v4, React Router Redux 5-alpha, and using a pretty similar solution to this one to show a custom modal.
Apparently the issue happens when entering the route via POP action (i.e. by directly accessing/reloading), but not if you come via PUSH (navigating from another route in the app).
Could this be related to the
historyapi instead (https://github.com/ReactTraining/history#blocking-transitions)?Edit: reference to an old issue in
history: https://github.com/ReactTraining/history/issues/367Same issue here as @arvinsingla described - I’m experiencing the exact same two scenarios, and they both seem to occur quite randomly.
I have a same issue with Prompt. Sometimes after clicking browser back button the hash url is returned back, if I say “do not redirect me”, sometimes no… But in both cases the browser is displaying the hash of previous page/state first. My issue is exactly what is described here https://4lvjoow5mw.codesandbox.io/
From what I can tell this isn’t a ‘bug’ per se.
There are two (or more) events that Prompt will capture - one of which is the
hashchangeevent. From what I’ve been able to reproduce thehashchangeevent is triggered after navigation.Promptis then catching the hashchange event, usingwindow.confirmto show the custom message. If the user clicks cancel,window.confirmreturns false and Prompt then returns us to our previous URL (hash).From what I’ve been able to tell there is no way around this;
beforeunloadis not valid for hash changes because the page isn’t actually unloaded (and it doesn’t support custom messages), andhashchangeis triggered after the hash change event occurs - which results in the ‘bug-like’ behavior of the URL changing to the ‘destination’ url, then back to the ‘original’.This also triggers the components on the original view/page to re-render.
The only robust solution would be to store form/user/state data in a context (or redux store) and then restore it when the user navigates back, but that’s a fairly heavy solution depending on the use case.
Anyone have any other ideas?
Thanks for this awesome library. Any update on this from official mentainer.
Hmm, that second case is interesting. The behavior is different between Chrome and Firefox (Firefox will go to the page two pages back).
In Firefox, clicking the back button once will open up the prompt. Clicking it a second time (while the prompt is open) will go to ‘google.com’. In Chrome, as you described above, the first click is the same, but the second click will go to ‘sandbox.com’. The Firefox behavior is what I would expect to happen.
I also forked your sandbox (https://codesandbox.io/s/l248ml7kj7) to add a third route so I could see what the behavior is when I can go back twice within the same site. When leaving the site entirely, that still works, but in both Chrome and Firefox it has weird behavior because it stacks prompts.
This double prompt issue is actually with the history package, so it would probably be best to work on it over there.
As for the original issue from here, we still need a reproducible test case, so if anyone wants to put one together, it would be appreciated.