Skip to content

Commit 6e143b4

Browse files
fix: let a navigation back to the shown route cancel a pending one (#719)
1 parent 14fdf54 commit 6e143b4

3 files changed

Lines changed: 77 additions & 3 deletions

File tree

‎.changeset/quiet-links-return.md‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@solidjs/router": patch
3+
---
4+
5+
Fixes a link back to the shown route being ignored while a navigation to another route is pending.

‎src/routing.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -612,8 +612,11 @@ export function createRouterContext(
612612
}
613613

614614
const current = reference();
615+
// While a navigation is pending, compare against where we are heading so
616+
// that navigating back to the shown route still cancels the pending one
617+
const target = lastTransitionTarget || { value: current, state: state() };
615618

616-
if (resolvedTo !== current || nextState !== state()) {
619+
if (resolvedTo !== target.value || nextState !== target.state) {
617620
if (isServer) {
618621
const e = getRequestEvent();
619622
e && (e.response = { status: 302, headers: new Headers({ Location: resolvedTo }) });
@@ -638,14 +641,15 @@ export function createRouterContext(
638641

639642
function navigateEnd(next: LocationChange) {
640643
const first = referrers[0];
641-
if (first) {
644+
// Ending up where the navigation started needs no new history entry
645+
if (first && (next.value !== first.value || next.state !== first.state)) {
642646
setSource({
643647
...next,
644648
replace: first.replace,
645649
scroll: first.scroll
646650
});
647-
referrers.length = 0;
648651
}
652+
referrers.length = 0;
649653
}
650654

651655
function preloadRoute(url: URL, preloadData?: boolean) {

‎test/pending-navigation.spec.tsx‎

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// @vitest-environment jsdom
2+
import { createResource, Suspense } from "solid-js";
3+
import { render } from "solid-js/web";
4+
import { MemoryRouter, Route, createMemoryHistory, useNavigate } from "../src/index.jsx";
5+
import type { Navigator } from "../src/types.js";
6+
7+
const wait = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
8+
9+
describe("pending navigation", () => {
10+
test("navigating back to the shown route cancels a pending navigation", async () => {
11+
let resolveSlow!: (value: string) => void;
12+
const Slow = () => {
13+
const [value] = createResource(() => new Promise<string>(resolve => (resolveSlow = resolve)));
14+
return <span>Slow {value()}</span>;
15+
};
16+
let navigate!: Navigator;
17+
const history = createMemoryHistory();
18+
history.set({ value: "/a", scroll: false });
19+
const root = document.createElement("div");
20+
const dispose = render(
21+
() => (
22+
<MemoryRouter
23+
history={history}
24+
root={props => {
25+
navigate = useNavigate();
26+
return (
27+
<>
28+
<p id="location">{props.location.pathname}</p>
29+
<Suspense fallback={<span>Loading</span>}>{props.children}</Suspense>
30+
</>
31+
);
32+
}}
33+
>
34+
<Route path="/" component={() => <span>Home</span>} />
35+
<Route path="/a" component={() => <span>Page A</span>} />
36+
<Route path="/slow" component={Slow} />
37+
</MemoryRouter>
38+
),
39+
root
40+
);
41+
42+
try {
43+
await wait(10);
44+
expect(root.textContent).toContain("Page A");
45+
navigate("/slow");
46+
await wait(10);
47+
expect(root.textContent).toContain("Page A");
48+
navigate("/a");
49+
await wait(10);
50+
resolveSlow("loaded");
51+
await wait(25);
52+
expect(history.get()).toBe("/a");
53+
expect(root.querySelector("#location")?.textContent).toBe("/a");
54+
expect(root.textContent).toContain("Page A");
55+
expect(root.textContent).not.toContain("Slow");
56+
57+
// no duplicate /a entry was pushed
58+
history.back();
59+
await wait(10);
60+
expect(history.get()).toBe("/");
61+
} finally {
62+
dispose();
63+
}
64+
});
65+
});

0 commit comments

Comments
 (0)