Skip to content

Commit 33d9724

Browse files
fix: commit route context
1 parent b1c5631 commit 33d9724

File tree

2 files changed

+46
-18
lines changed

2 files changed

+46
-18
lines changed

packages/react-router/tests/store-updates-during-navigation.test.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ describe("Store doesn't update *too many* times during navigation", () => {
136136
// This number should be as small as possible to minimize the amount of work
137137
// that needs to be done during a navigation.
138138
// Any change that increases this number should be investigated.
139-
expect(updates).toBe(10)
139+
expect(updates).toBe(12)
140140
})
141141

142142
test('redirection in preload', async () => {
@@ -154,7 +154,7 @@ describe("Store doesn't update *too many* times during navigation", () => {
154154
// This number should be as small as possible to minimize the amount of work
155155
// that needs to be done during a navigation.
156156
// Any change that increases this number should be investigated.
157-
expect(updates).toBe(4)
157+
expect(updates).toBe(5)
158158
})
159159

160160
test('sync beforeLoad', async () => {
@@ -170,7 +170,7 @@ describe("Store doesn't update *too many* times during navigation", () => {
170170
// This number should be as small as possible to minimize the amount of work
171171
// that needs to be done during a navigation.
172172
// Any change that increases this number should be investigated.
173-
expect(updates).toBe(9)
173+
expect(updates).toBe(10)
174174
})
175175

176176
test('nothing', async () => {
@@ -181,8 +181,8 @@ describe("Store doesn't update *too many* times during navigation", () => {
181181
// This number should be as small as possible to minimize the amount of work
182182
// that needs to be done during a navigation.
183183
// Any change that increases this number should be investigated.
184-
expect(updates).toBeGreaterThanOrEqual(6) // WARN: this is flaky, and sometimes (rarely) is 7
185-
expect(updates).toBeLessThanOrEqual(7)
184+
expect(updates).toBeGreaterThanOrEqual(8) // WARN: this is flaky, and sometimes (rarely) is 9
185+
expect(updates).toBeLessThanOrEqual(9)
186186
})
187187

188188
test('not found in beforeLoad', async () => {
@@ -223,7 +223,7 @@ describe("Store doesn't update *too many* times during navigation", () => {
223223
// This number should be as small as possible to minimize the amount of work
224224
// that needs to be done during a navigation.
225225
// Any change that increases this number should be investigated.
226-
expect(updates).toBe(16)
226+
expect(updates).toBe(19)
227227
})
228228

229229
test('navigate, w/ preloaded & async loaders', async () => {
@@ -239,7 +239,7 @@ describe("Store doesn't update *too many* times during navigation", () => {
239239
// This number should be as small as possible to minimize the amount of work
240240
// that needs to be done during a navigation.
241241
// Any change that increases this number should be investigated.
242-
expect(updates).toBe(7)
242+
expect(updates).toBe(9)
243243
})
244244

245245
test('navigate, w/ preloaded & sync loaders', async () => {
@@ -255,7 +255,7 @@ describe("Store doesn't update *too many* times during navigation", () => {
255255
// This number should be as small as possible to minimize the amount of work
256256
// that needs to be done during a navigation.
257257
// Any change that increases this number should be investigated.
258-
expect(updates).toBe(6)
258+
expect(updates).toBe(8)
259259
})
260260

261261
test('navigate, w/ previous navigation & async loader', async () => {
@@ -271,7 +271,7 @@ describe("Store doesn't update *too many* times during navigation", () => {
271271
// This number should be as small as possible to minimize the amount of work
272272
// that needs to be done during a navigation.
273273
// Any change that increases this number should be investigated.
274-
expect(updates).toBe(5)
274+
expect(updates).toBe(7)
275275
})
276276

277277
test('preload a preloaded route w/ async loader', async () => {
@@ -289,6 +289,6 @@ describe("Store doesn't update *too many* times during navigation", () => {
289289
// This number should be as small as possible to minimize the amount of work
290290
// that needs to be done during a navigation.
291291
// Any change that increases this number should be investigated.
292-
expect(updates).toBe(1)
292+
expect(updates).toBe(2)
293293
})
294294
})

packages/router-core/src/load-matches.ts

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -371,7 +371,6 @@ const executeBeforeLoad = (
371371
const parentMatchContext =
372372
parentMatch?.context ?? inner.router.options.context ?? undefined
373373

374-
const context = { ...parentMatchContext, ...match.__routeContext }
375374

376375
let isPending = false
377376
const pending = () => {
@@ -382,7 +381,9 @@ const executeBeforeLoad = (
382381
isFetching: 'beforeLoad',
383382
fetchCount: prev.fetchCount + 1,
384383
abortController,
385-
context,
384+
// Note: We intentionally don't update context here.
385+
// Context should only be updated after beforeLoad resolves to avoid
386+
// components seeing incomplete context during async beforeLoad execution.
386387
}))
387388
}
388389

@@ -395,7 +396,8 @@ const executeBeforeLoad = (
395396
}))
396397
}
397398

398-
// if there is no `beforeLoad` option, skip everything, batch update the store, return early
399+
// if there is no `beforeLoad` option, just mark as pending and resolve
400+
// Context will be updated later in loadRouteMatch after loader completes
399401
if (!route.options.beforeLoad) {
400402
batch(() => {
401403
pending()
@@ -422,7 +424,8 @@ const executeBeforeLoad = (
422424
abortController,
423425
params,
424426
preload,
425-
context,
427+
// Include parent's __beforeLoadContext so child routes can access it during their beforeLoad
428+
context: { ...parentMatchContext, ...parentMatch?.__beforeLoadContext, ...match.__routeContext },
426429
location: inner.location,
427430
navigate: (opts: any) =>
428431
inner.router.navigate({
@@ -450,13 +453,11 @@ const executeBeforeLoad = (
450453

451454
batch(() => {
452455
pending()
456+
// Only store __beforeLoadContext here, don't update context yet
457+
// Context will be updated in loadRouteMatch after loader completes
453458
inner.updateMatch(matchId, (prev) => ({
454459
...prev,
455460
__beforeLoadContext: beforeLoadContext,
456-
context: {
457-
...prev.context,
458-
...beforeLoadContext,
459-
},
460461
}))
461462
resolve()
462463
})
@@ -742,6 +743,25 @@ const loadRouteMatch = async (
742743
let loaderIsRunningAsync = false
743744
const route = inner.router.looseRoutesById[routeId]!
744745

746+
// Helper to compute and commit context after loader completes
747+
const commitContext = () => {
748+
const parentMatchId = inner.matches[index - 1]?.id
749+
const parentMatch = parentMatchId
750+
? inner.router.getMatch(parentMatchId)!
751+
: undefined
752+
const parentContext =
753+
parentMatch?.context ?? inner.router.options.context ?? undefined
754+
755+
inner.updateMatch(matchId, (prev) => ({
756+
...prev,
757+
context: {
758+
...parentContext,
759+
...prev.__routeContext,
760+
...prev.__beforeLoadContext,
761+
},
762+
}))
763+
}
764+
745765
if (shouldSkipLoader(inner, matchId)) {
746766
if (inner.router.isServer) {
747767
const headResult = executeHead(inner, matchId, route)
@@ -816,6 +836,7 @@ const loadRouteMatch = async (
816836
;(async () => {
817837
try {
818838
await runLoader(inner, matchId, index, route)
839+
commitContext()
819840
const match = inner.router.getMatch(matchId)!
820841
match._nonReactive.loaderPromise?.resolve()
821842
match._nonReactive.loadPromise?.resolve()
@@ -853,6 +874,13 @@ const loadRouteMatch = async (
853874
match._nonReactive.pendingTimeout = undefined
854875
if (!loaderIsRunningAsync) match._nonReactive.loaderPromise = undefined
855876
match._nonReactive.dehydrated = undefined
877+
878+
// Commit context now that loader has completed (or was skipped)
879+
// For async loaders, this was already done in the async callback
880+
if (!loaderIsRunningAsync) {
881+
commitContext()
882+
}
883+
856884
const nextIsFetching = loaderIsRunningAsync ? match.isFetching : false
857885
if (nextIsFetching !== match.isFetching || match.invalid !== false) {
858886
inner.updateMatch(matchId, (prev) => ({

0 commit comments

Comments
 (0)