From 4ef6e829ff6d8f1424d989281f824ed26c5840a8 Mon Sep 17 00:00:00 2001 From: thelastbackspace <23155651+thelastbackspace@users.noreply.github.com> Date: Thu, 5 Nov 2026 09:06:28 +0100 Subject: [PATCH] fix: let a navigation back to the shown route cancel a pending one --- .changeset/quiet-links-return.md | 5 +++ src/routing.ts | 10 +++-- test/pending-navigation.spec.tsx | 65 ++++++++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 3 deletions(-) create mode 100644 .changeset/quiet-links-return.md create mode 100644 test/pending-navigation.spec.tsx diff --git a/.changeset/quiet-links-return.md b/.changeset/quiet-links-return.md new file mode 100644 index 00000000..69ce05e0 --- /dev/null +++ b/.changeset/quiet-links-return.md @@ -0,0 +1,5 @@ +--- +"@solidjs/router": patch +--- + +Fixes a link back to the shown route being ignored while a navigation to another route is pending. diff --git a/src/routing.ts b/src/routing.ts index eb20bb06..222c4aa6 100644 --- a/src/routing.ts +++ b/src/routing.ts @@ -612,8 +612,11 @@ export function createRouterContext( } const current = reference(); + // While a navigation is pending, compare against where we are heading so + // that navigating back to the shown route still cancels the pending one + const target = lastTransitionTarget || { value: current, state: state() }; - if (resolvedTo !== current || nextState !== state()) { + if (resolvedTo !== target.value || nextState !== target.state) { if (isServer) { const e = getRequestEvent(); e && (e.response = { status: 302, headers: new Headers({ Location: resolvedTo }) }); @@ -638,14 +641,15 @@ export function createRouterContext( function navigateEnd(next: LocationChange) { const first = referrers[0]; - if (first) { + // Ending up where the navigation started needs no new history entry + if (first && (next.value !== first.value || next.state !== first.state)) { setSource({ ...next, replace: first.replace, scroll: first.scroll }); - referrers.length = 0; } + referrers.length = 0; } function preloadRoute(url: URL, preloadData?: boolean) { diff --git a/test/pending-navigation.spec.tsx b/test/pending-navigation.spec.tsx new file mode 100644 index 00000000..254d1f95 --- /dev/null +++ b/test/pending-navigation.spec.tsx @@ -0,0 +1,65 @@ +// @vitest-environment jsdom +import { createResource, Suspense } from "solid-js"; +import { render } from "solid-js/web"; +import { MemoryRouter, Route, createMemoryHistory, useNavigate } from "../src/index.jsx"; +import type { Navigator } from "../src/types.js"; + +const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms)); + +describe("pending navigation", () => { + test("navigating back to the shown route cancels a pending navigation", async () => { + let resolveSlow!: (value: string) => void; + const Slow = () => { + const [value] = createResource(() => new Promise(resolve => (resolveSlow = resolve))); + return Slow {value()}; + }; + let navigate!: Navigator; + const history = createMemoryHistory(); + history.set({ value: "/a", scroll: false }); + const root = document.createElement("div"); + const dispose = render( + () => ( + { + navigate = useNavigate(); + return ( + <> +

{props.location.pathname}

+ Loading}>{props.children} + + ); + }} + > + Home} /> + Page A} /> + +
+ ), + root + ); + + try { + await wait(10); + expect(root.textContent).toContain("Page A"); + navigate("/slow"); + await wait(10); + expect(root.textContent).toContain("Page A"); + navigate("/a"); + await wait(10); + resolveSlow("loaded"); + await wait(25); + expect(history.get()).toBe("/a"); + expect(root.querySelector("#location")?.textContent).toBe("/a"); + expect(root.textContent).toContain("Page A"); + expect(root.textContent).not.toContain("Slow"); + + // no duplicate /a entry was pushed + history.back(); + await wait(10); + expect(history.get()).toBe("/"); + } finally { + dispose(); + } + }); +});