kit
kit copied to clipboard
pushState/replaceState don't trigger beforeNavigate
Describe the bug
I just started using pushState/replaceState and realized that they don't trigger beforeNavigate, and so I cannot ask the user to confirm before navigating away from a modal form.
I have a rather big webapp that will need to have complex forms in modals. Users definitely want to be asked for confirmation before losing complex form state.
Reproduction
This modal will ask for confirmation when clicking the cancel button, but not on pressing back in the browser: https://stackblitz.com/edit/sveltejs-kit-template-default-fsoasb?file=src%2Froutes%2FModal.svelte
There's a beforeNavigate that should be showing the confirmation, but it isn't called.
Logs
No response
System Info
Forked the newest Sveltekit Stackblitz.
Severity
serious, but I can work around it
That is that for now I can live with only showing a confirmation when pressing the close button in the ui.
Additional Information
No response
pushState and replaceState do not perform an actual navigation (shallow navigation) as it does not change the underlying route. The only things that do change are:
- The browser URL.
- The browser history entry (push/replace).
$page.stateif specified.
In comparison, (real navigations) using goto on the client, redirect in a load function, clicking on a link, will change these:
$page.url,$page.route, etc.- Navigation hooks e.g.,
beforeNavigate. - The browser URL.
- The browser history entry (push/replace with goto).
So there's no way to intercept a shallow navigation back and show a confirmation dialog before doing the back navigation? Shouldn't this be possible?
+1 experiencing this issue aswel only workaround i found was doing something like this
<svelte:window
on:popstate={(e) => {
if (form_is_tainted) {
history.pushState(null, '', window.location.href);
}
}} />
which is discouraged becasue it interferes with svelte kit router
@eltigerchino can you see any reason why letting beforeNavigate fire would be an issue? I can't really come up with a reason myself. In that case should this not be considered a bug? since the documentation for shallow routing literally suggests using it as a model that can be dismissed with the back button.
If there is a good reason why beforeNavigate would be a problematic solution could something akin to beforeShallowNavigate and similar hooks be a solution?
I also think it's a bug that page params doesn't seem to being updated on popstate (spa mode). Seems like there are multiple issues here.
ref: https://github.com/paoloricciuti/sveltekit-search-params/issues/80#issuecomment-2634340592
Currently experiencing the same issue. Came here looking for a workaround. I'm using forms inside modals and wanted to give the mobile users a way to swipe back instead of clicking on close/cancel. The shallow routing works fine, but now I'm missing a way to show unsaved changes alert if the form is dirty/tainted.
No workaround, and no word from anyone on the Svelte team on whether they want to support this.
@Rich-Harris any chance we can get a direction on this on what the team would like done here. Hopefully that drives a community pr.
@niemyjski we can at least provide some discussion, so when a maintainer takes a look they can decide on a direction which we could create a PR for.
Possible solutions
-
Letting
beforeNavigatefire normally on shallow route changes I can see why this may not be the desired solution since @eltigerchino is right, that beforeNavigate signifies changes to the URL which a shallow navigation is not. -
Merging normal navigation and shallow navigation events into beforeNavigate This could be simply extending the "type" argument of the beforeNavigate function
beforeNavigate(({type}) => {
// (type: "form" | "leave" | "link" | "goto" | "popstate") & | "shallow"
});
- Adding a
beforeStateUpdatehook, that fires when you attempt to change pageState but before it's applied
<script lang="ts">
pushState('some-page', { foo: 'bar' });
beforeStateChange((changeEvent) => {
// changeEvent: {
// from: App.PageState
// to: App.PageState
// cancel: () => void
// }
})
</script>
I think this solution would give the most flexibility and possibly be applicable to other shallow navigation scenarios. It would probably be my preferred solution, however it does introduce yet another hook that would need to be documented, maintained and explained.
What are your thoughts on these possible solutions?
- beforeNavigate signifies changes to the URL which a shallow navigation is not.
@AndreasHald could you explain this more? Since I think you can change the URL in the shallow navigation. From the docs: The first argument to pushState is the URL, relative to the current URL. To stay on the current URL, use ''. from this link.
Yeah this is very badly formulated by me, I apologise.
My point was that the whole idea of shallow navigation, is that you are on one page, and you "overlay" another page. You are correct, in that technically the URL in the browser changes to the overlayed page. However the +page.svelte in use, is the original page
So you may imagine a scenario where your routes are like this:
/table /table/{details-page-id}
Where /table displays a table and /table/{details-page-id} displays a page for a single row in the table.
Then you may wan't to display the details page in a modal instead of doing full navigation, to preserve the table state beneath it. This would probably serve as a better user experience.
So when you click a row you navigate shallowly, which means the +page of /table is still in effect, you just load code and data for another page and display that on top of it. And you do change the URL to that of /table/{details-page-id} which means that if you copy the url of a table with a details page opened you correctly go to that page.
So you DO change the URL but not the route / page you are on.
@AndreasHald Thanks for the very thorough explanation, I think I understand how the shallow routing works now. Hopefully we'll get an answer from the team which could drive a community pull-request. I can see this requested feature being used in bigger applications later on down the road.