Router misalignment with the History API when navigation is prevented
Describe the bug
In a simple SPA app with solid and solidjs router, if I navigate from /home to /page1 to /page2 and then click the browser back button but cancel the navigation, the next time I click the browser back button if I confirm the navigation the destination page is /home and not /page1. The content of the corresponding page is also not being displayed.
Your Example Website or App
https://github.com/Gust99/solidjs-router-app
Steps to Reproduce the Bug or Issue
- Run
npm run dev - Go to localhost:5173
- Click the page1 link
- Click the page2 link
- Click the browser back button
- Cancel navigation
- Click again the browser back button
- Confirm navigation
Expected behavior
- I should navigate to page1 after confirming the navigation the second time I try to go from page2 to page1.
- The corresponding content of the route should be displayed.
Screenshots or Videos
Platform
- OS: Windows
- Browser: Chrome
- Version: 138.0.7204.157
Additional context
No response
Hi, I'd like to work on this issue. I have experience with SolidJS and routing behavior in SPAs, and I’m confident I can help identify and fix the navigation problem. Please assign it to me when possible. Thanks!
Hi @ryansolid,
could be possible that someone fixes this bug or could you assign it to @DanishIbrahimKhan please? Is this out of the scope of solidjs router?
Thanks
I assumed that enough time had passed for the issue to be forgotten and that the maintainers might be too busy to micromanage the issue, so I looked into it.
You cannot prevent all browser actions through source code alone. Because browser APIs, such as window.history.go(), can behave differently across browsers, it is possible that not all processing is finished on the browser end after calling e.preventdefault, and it is desirable to take additional actions after all associated processing has finished.
See also: https://developer.mozilla.org/en-US/docs/Web/API/Window/popstate_event#the_history_stack
Browsers tend to handle the popstate event differently on page load. Chrome (prior to v34) and Safari always emit a popstate event on page load, but Firefox doesn't.
Note: When writing functions that process popstate event it is important to take into account that properties like window.location will already reflect the state change (if it affected the current URL), but document might still not. If the goal is to catch the moment when the new document state is already fully in place, a zero-delay setTimeout() method call should be used to effectively put its inner callback function that does the processing at the end of the browser event loop: window.onpopstate = () => setTimeout(doSomeThing, 0);
In this case, it is up to the individual user to recognize and adhere to the usage of the combined browser history/popup/useBeforeLeave, which requires that window.confirm()
not be called synchronously, but rather wait for the browser to complete processing and then call it via setTimeout, etc.
This page, created as described in the original article, works fine for backtracking.
import { useBeforeLeave, type BeforeLeaveEventArgs } from "@solidjs/router"
export default function Page2() {
useBeforeLeave((e: BeforeLeaveEventArgs) => {
if (!e.defaultPrevented) {
// preventDefault to block immediately and prompt user async
e.preventDefault();
setTimeout(() => {
if (window.confirm("Discard unsaved changes - are you sure?")) {
// user wants to proceed anyway so retry with force=true
e.retry(true);
}
}, 100);
}
});
return <div>Page2</div>
}
However, window.history.forward() was not preserving history like back(), so I fixed(#536) that.
Maybe he did it for some reason at the time, or maybe it was just a typo, but this change makes it so that the browser history on forward is preserved and works just as well as it does on back, and it feels logically correct.
Hi @dennev
I found this other issue, does your PR addresses this bug as well?
- /home -> /page1
- /page1 -> /page2
- Right click - back button
- Select /home from dropdown (Not sure why it is not showing in the gif)
- Cancel navigation
Expected result: We stay in page2, history API is preserved Actual result: page2 component is still there but url is "/" and history stack changes
Page2 Component:
Clicking the dropdown button in step 4 moves the history backwards,
And hitting the cancel button in step 5 moves it forward again.
Currently, my PR that fixed the forward is not applied, so the problem is reproduced.
I checked that my PR with the fixed solid-router works fine in this situation as expected. We have to wait for the maintainer's approval.
Thanks for the explanation