Loading workspace insights... Statistics interval
7 days30 daysLatest CI Pipeline Executions
1769f5f9 fix(router-core): keep latest load alive through match commit
A router load now records its controlled load promise as latest before the
transition body starts. This gives the load a stable identity for the entire
navigation, including the synchronous part of startTransition.
Pass that identity into loadMatches so stale loads cannot call onReady and
promote their pending matches after a newer load has started. Also guard the
onReady commit itself before scheduling the view-transition update and again
inside the update callback, so a load that becomes stale while waiting for the
view transition cannot mutate the active match stores.
Add a local commitPromise around the pending-to-active match commit. The
router's startViewTransition wrapper is fire-and-forget, so without this latch
onReady could resolve, loadMatches could finish, and load() could clear
latestLoadPromise before the view-transition update callback actually committed
the matches. React can then observe a stale pending/redirected match without an
in-flight load promise to suspend on, which is the blank-render race reproduced
by the issue-7120 e2e test.
Only the latest load now writes global redirect/status state, resolves the
navigation commit promise, and clears latestLoadPromise. Stale loads still
resolve their own load promise so callers do not hang, but they cannot complete
the router-level commit for a newer navigation.
Also make load() resolve only the commitLocationPromise that belonged to that
specific load.
Previously, load() resolved this.commitLocationPromise at completion time. That
field is mutable and can be replaced by a later navigate()/commitLocation()
before the older load finishes. In most navigation paths latestLoadPromise keeps
that safe, because the newer navigation also starts a newer load. But an async
blocker can create a window where a newer commitLocationPromise has already been
installed while its corresponding load has not started yet.
In that window, the older load could resolve the newer navigation's promise
early. The caller awaiting navigate() would observe the navigation as complete
even though the blocker had not released and the target route had not loaded.
Capture this.commitLocationPromise when load() starts, resolve only that captured
promise, and clear the router field only if it still points at the same promise.
This preserves the ownership chain between commitLocation() and the load that is
actually completing, while still allowing newer navigations to replace the global
commit promise safely.
Add a router-core regression test where an onEnter callback starts a second
navigation that is held by an async blocker. Without this fix, the second
navigate() promise resolves before the blocker is released; with the fix, it
stays pending until the blocked navigation actually completes.