Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-navigate-commit-window-drop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/router": patch
---

Rebuild navigation commitment on Solid's transition engine. Programmatic and native navigation now write one canonical location source, repeated writes use engine last-write-wins, and history updates only after the winning transition settles. This fixes dropped soft navigations and native/programmatic races while preserving redirect options, pending link/search state, and lazy-route error delivery.
36 changes: 29 additions & 7 deletions src/routers/factory.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
/*@refresh skip*/

import type { Owner } from "solid-js";
import { createSignal, getOwner, onCleanup, sharedConfig, untrack } from "solid-js";
import {
createSignal,
getOwner,
onCleanup,
onSettled,
runWithOwner,
sharedConfig,
untrack
} from "solid-js";
// standalone import: `DEV` is undefined in solid's production build, so app
// bundlers fold `DEV &&` diagnostics out of shipped bundles
import { DEV } from "solid-js";
Expand Down Expand Up @@ -199,27 +207,41 @@ export interface RouterInstance<R extends readonly RouteDefinition[] = RouteDefi

/** Wraps a history adapter in the integration signal the router core consumes. Must run under a reactive owner. */
function createIntegration(history: RouterHistory): RouterIntegration {
let ignore = false;
let committing = false;
const wrap = (value: string | LocationChange) => (typeof value === "string" ? { value } : value);
const [read, write] = createSignal(wrap(history.get()), {
equals: (a, b) => a.value === b.value && a.state === b.state,
equals: (a, b) =>
a.value === b.value && a.state === b.state && a._navigation === b._navigation,
ownedWrite: true
});
const signal: RouterIntegration["signal"] = [
read,
(next: LocationChange) => {
!ignore && history.set(next);
if (sharedConfig.registry && !sharedConfig.done) sharedConfig.done = true;
write(next);
if (next._navigation && next._navigation > 0) {
// Register out of band so a destination error boundary replacing the
// Router subtree cannot suppress the winning history commit.
runWithOwner(null, () =>
onSettled(() => {
if (read() !== next) return;
committing = true;
try {
history.set(next);
} finally {
committing = false;
}
})
);
}
}
];

history.init &&
onCleanup(
history.init((value = history.get()) => {
ignore = true;
signal[1](wrap(value));
ignore = false;
if (committing) return;
signal[1]({ ...wrap(value), _navigation: -1 });
})
);

Expand Down
Loading
Loading