-
Notifications
You must be signed in to change notification settings - Fork 26.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Browser Back Button does not trigger window.onbeforeunload #2694
Comments
The One of the most important difference between SPA and multip-page websites is that the window will never unload its resources when user is navigating between different "views" in a SPA. The browser never does full-page reload unless user is hitting the "refresh" button. next.js provide some callback such as See #2236 |
I'd be interested in fixing this bug. Can I take over? |
@ppatel221 yes. You can take this. |
Very interested in this feature. Would greatly improve experience on forms in my side project. |
any updates on this? |
Up. Hopefully, somebody will fix that. |
Note that there is a router event for going back now: https://github.com/zeit/next.js#intercepting-popstate |
@timneutkens Intercepting popstate will work when the user clicks the back button or forward button but will not work if a user clicks on a link to navigate to another page. Is it possible to have |
Agree with @rameshanandakrishnan, the popstate handler is great when going back but not for click handling. The beforeRouteChangeStart which is proposed in the PR would be exactly what I need to be able to intercept any route change and cancel it if needed. I think it's kind of mandatory for big forms to avoid loosing it. |
As per #5377 (comment) |
This comment has been minimized.
This comment has been minimized.
Following code work for me. 🚀
|
@zakbutcher how did you solve this issue finally? |
facing this problem too, anybody came up with a fix ? its been 3 years now :/ |
@Nitaaq Try this snippet that will work. |
@ahtashamabbasse it will work, but instead of a native alert i'd like to show a custom html dialog, i was able to do that too but whenever the user presses the back button that is in the browser, the link in the address bar changes and the custom dialog appears which is not the intended behavior. the thing i want to achieve is the same as react-router s Prompt |
@ahtashamabbasse Your snippet does not address this issue at all. There is a good discussion on this issue, where the example of emiting This is the workaround I used and it seems to work so far: // Use beforeunload to prevent closing the tab, refreshing the page or moving outside the Next app
useEffect(() => {
window.addEventListener('beforeunload', handleWindowClose);
return () => {
window.removeEventListener('beforeunload', handleWindowClose);
};
});
// Use routeChangeStart to prevent navigation inside of the Next app
// Uses a module variable to bypass the confirm, otherwise we would be in a loop
router.events.on('routeChangeStart', () => {
if (leaveConfirmed) return;
if (window.confirm('Are you sure you want to leave this page?'))
leaveConfirmed = true;
else {
router.events.emit('routeChangeError');
throw 'routeChange aborted.';
}
});
// Set the module variable to false on component mount
useEffect(() => {
leaveConfirmed = false;
}, []); |
Thanks for that snippet @Fensterbank. Super useful. I did notice one possible issue with it. Which I think is what prompted you to use the leaveConfirmed variable. The function gets added as a listener to the event each time the component is rendered. So the page may end up creating hundreds of copies of that function, and calling each one of those copies when a user browses internally. And these never get cleared. Of course that might not be an issue in many use cases. But it was a problem with my particular scenario. Either way, I might suggest adding the router event listener in an event hook, just like the window Here is a version with this approach, for anyone who gets to this page looking for another solution. Note, I have adapted it a bit further for my requirements. // prompt the user if they try and leave with unsaved changes
useEffect(() => {
const warningText =
'You have unsaved changes - are you sure you wish to leave this page?';
const handleWindowClose = (e: BeforeUnloadEvent) => {
if (!unsavedChanges) return;
e.preventDefault();
return (e.returnValue = warningText);
};
const handleBrowseAway = () => {
if (!unsavedChanges) return;
if (window.confirm(warningText)) return;
router.events.emit('routeChangeError');
throw 'routeChange aborted.';
};
window.addEventListener('beforeunload', handleWindowClose);
router.events.on('routeChangeStart', handleBrowseAway);
return () => {
window.removeEventListener('beforeunload', handleWindowClose);
router.events.off('routeChangeStart', handleBrowseAway);
};
}, [unsavedChanges]); So far, it seems to work pretty reliably. |
@roderickhodgson Thanks for your reply. Good catch, after reading this I'm pretty sure exactly this was the case why I just dirty bypassed this with the module variable. 🙈 I will switch to your approach. |
HI @roderickhodgson ! Thanks for sharing the code works correctly for me. But how do I detect a type = submit? In that case I shouldn't be blocked. |
This is gold. |
Great contribution. Thx @roderickhodgson 🙌🏻 |
The solution provided by @roderickhodgson Isn't really working out for me. it does block navigation really well in all the mentioned cases, but the issue I have is with window.history and pathname. Is there a solution for handling the page state after throwing an error in the "routeChangeStart" when triggered by navigating back via browser the same as in "beforeunload" when page state is returned if the user chooses to stay on the page. |
I see a lot of solutions that depends on the router. But this is just trading a dependency from React Router to Next Router, we will have the same issue in 10 year when there is a new version of the Router and we must change everything. I don't really see how it could be better decoupled, maybe using events?
React Router never seems to have solved this issue and for a lot of us that's the main issue. This is not Next specific. |
If I am not missing something, this is not exactly a perfect solution. If I use I could get away with not using |
When I click on back button the pathname changes I expected it to be restored if the user clicks on cancel button. Anyone facing this issue? |
I use this a bit differently, but the idea is clear.
|
Thank you for the solution, it worked perfectly. However on Safari Mobile it works when a navigate through application links but the back button doesn't work. Did anyone experienced this? |
you need to call it only when you are blocking the redirect. |
useEffect(() => {
const warningText =
'You have unsaved changes - are you sure you wish to leave this page?';
const handleWindowClose = (e: BeforeUnloadEvent) => {
if (!unsavedChanges) return;
e.preventDefault();
return (e.returnValue = warningText);
};
const handleBrowseAway = () => {
if (!unsavedChanges) return;
if (window.confirm(warningText)) return;
Router.events.emit('routeChangeError');
//push state, because browser back action changes link and changes history state
// but we stay on the same page
if (Router.asPath !== window.location.pathname) {
window.history.pushState('', '', Router.asPath);
}
throw 'routeChange aborted.';
};
window.addEventListener('beforeunload', handleWindowClose);
Router.events.on('routeChangeStart', handleBrowseAway);
return () => {
window.removeEventListener('beforeunload', handleWindowClose);
Router.events.off('routeChangeStart', handleBrowseAway);
};
}, [unsavedChanges]); I'm doing like this, however, the back button on safari mobile still does nothing. It's a weird behaviour. Only ios safary works like this. |
@uDaniAlves Facing the same issue on Safari Mobile. Were you able to get a fix for this? |
This issue has been automatically locked due to no recent activity. If you are running into a similar issue, please create a new issue with the steps to reproduce. Thank you. |
In our application we have forms and want to prompt the user when they click the browser's Back or Forward buttons to notify them they will lose their data if they continue.
Although it is similar in nature to this issue that has been marked as Exploration
Expected Behavior
When user clicks back button,
window.onbeforeunload
function that is defined firesCurrent Behavior
Browser redirects user to previous page without triggering
window.onbeforeunload
actionSpecial Notes
If I navigate directly to the page with the form by typing in the url, clicking the Back button correctly triggers
window.onbeforeunload
.If I navigate away from the page with the form by typing in the url for another page, the
window.onbeforeunload
event fires correctly.If I navigate to the page using links within the application, clicking the Back button does not trigger
window.onbeforeunload
.I think this has something to do with they way the browser is treating our application when the user navigates within the application using Next Links. Instead of seeing each view as its own page and loading/unloading the page resources as the user navigates, it sees the entire application as just one page, thereby never loading/unloading resources and thus not triggering
window.onbeforeunload
.The text was updated successfully, but these errors were encountered: