solid-router icon indicating copy to clipboard operation
solid-router copied to clipboard

Router misalignment with the History API when navigation is prevented

Open Gust99 opened this issue 5 months ago • 6 comments

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

  1. Run npm run dev
  2. Go to localhost:5173
  3. Click the page1 link
  4. Click the page2 link
  5. Click the browser back button
  6. Cancel navigation
  7. Click again the browser back button
  8. 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

Image

Platform

  • OS: Windows
  • Browser: Chrome
  • Version: 138.0.7204.157

Additional context

No response

Gust99 avatar Jul 17 '25 14:07 Gust99

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!

DanishIbrahimKhan avatar Jul 17 '25 14:07 DanishIbrahimKhan

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

Gust99 avatar Jul 21 '25 14:07 Gust99

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.

dennev avatar Aug 10 '25 02:08 dennev

Hi @dennev

I found this other issue, does your PR addresses this bug as well?

Image

  1. /home -> /page1
  2. /page1 -> /page2
  3. Right click - back button
  4. Select /home from dropdown (Not sure why it is not showing in the gif)
Image
  1. 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

Image Image

Page2 Component:

Image

Gust99 avatar Aug 20 '25 15:08 Gust99

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.

dennev avatar Aug 20 '25 22:08 dennev

Thanks for the explanation

Gust99 avatar Aug 21 '25 13:08 Gust99