Skip to content
Open
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/violet-poets-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@tanstack/router-core': patch
---

Fix context value from a parent route's `beforeLoad` not being propagated to a sub-route while the sub-route's loader is reloading in the background
62 changes: 62 additions & 0 deletions packages/react-router/tests/routeContext.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2796,6 +2796,68 @@ describe('useRouteContext in the component', () => {
expect(content).toBeInTheDocument()
})

test('context value from beforeLoad is propagated to a sub-route while its loader reloads in the background', async () => {
let sawUndefinedContext = false

const rootRoute = createRootRoute({
component: () => <Outlet />,
})
const homeRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/',
component: () => <div>Home page</div>,
})
const contextPropagationRoute = createRoute({
getParentRoute: () => rootRoute,
path: '/context-propagation',
beforeLoad: () => ({ number: 42 }),
component: () => <Outlet />,
})
const contextPropagationIndexRoute = createRoute({
getParentRoute: () => contextPropagationRoute,
path: '/',
staleTime: 0,
loader: async () => {
await sleep(WAIT_TIME)
},
component: () => {
const { number } = contextPropagationIndexRoute.useRouteContext()
sawUndefinedContext ||= number === undefined

return (
<div>
number = {String(number)}, saw undefined ={' '}
{String(sawUndefinedContext)}
</div>
)
},
})

const routeTree = rootRoute.addChildren([
homeRoute,
contextPropagationRoute.addChildren([contextPropagationIndexRoute]),
])
const router = createRouter({ routeTree, history })

render(<RouterProvider router={router} />)

await act(() => router.navigate({ to: '/context-propagation' }))

expect(
await screen.findByText('number = 42, saw undefined = false'),
).toBeInTheDocument()

await act(() => router.navigate({ to: '/' }))

expect(await screen.findByText('Home page')).toBeInTheDocument()

act(() => router.history.back())

expect(
await screen.findByText('number = 42, saw undefined = false'),
).toBeInTheDocument()
})
Comment on lines +2799 to +2859

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The regression test does not yet prove the in-flight background reload scenario.

Current checks validate the final state after findByText(...), so this can pass even if render waits for loader completion (or if background reload behavior regresses). The test should assert context while the second loader run is actively pending.

Suggested tightening
 import {
   act,
   cleanup,
   configure,
   fireEvent,
   render,
   screen,
+  waitFor,
 } from '`@testing-library/react`'
@@
   test('context value from beforeLoad is propagated to a sub-route while its loader reloads in the background', async () => {
     let sawUndefinedContext = false
+    let loaderCalls = 0
+    let releaseSecondLoad!: () => void
+    const secondLoadStarted = vi.fn()
+    const secondLoadGate = new Promise<void>((resolve) => {
+      releaseSecondLoad = resolve
+    })
@@
       staleTime: 0,
       loader: async () => {
-        await sleep(WAIT_TIME)
+        loaderCalls += 1
+        if (loaderCalls === 2) {
+          secondLoadStarted()
+          await secondLoadGate
+        }
       },
@@
     act(() => router.history.back())
+    await waitFor(() => expect(secondLoadStarted).toHaveBeenCalledOnce())
 
     expect(
-      await screen.findByText('number = 42, saw undefined = false'),
+      screen.getByText('number = 42, saw undefined = false'),
     ).toBeInTheDocument()
+    releaseSecondLoad()
   })

Based on learnings from the PR objectives and review stack context, this test should explicitly validate context propagation during the background reload window.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/react-router/tests/routeContext.test.tsx` around lines 2799 - 2859,
The test currently only asserts the final rendered text after loader completion,
so it doesn't prove context propagation during an in-flight background reload;
change the test (the one named "context value from beforeLoad..." using
contextPropagationRoute, contextPropagationIndexRoute, beforeLoad, loader and
useRouteContext) to assert the route context value while the second loader run
is still pending: make the loader delay (already uses sleep), start the
navigation that triggers the background reload, then assert immediately (without
awaiting loader completion or findByText that waits) that the component still
reads number === 42 (e.g., use a non-awaiting query/get or waitFor with a short
timeout to capture the interim render) before letting the loader finish so the
test verifies context propagation during the in-flight reload.


// Check if context that is updated at the root, is the same in the root route
test('modified route context, present in the root route', async () => {
const rootRoute = createRootRoute({
Expand Down
1 change: 1 addition & 0 deletions packages/router-core/src/load-matches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,7 @@ const loadRouteMatch = async (
shouldReloadInBackground
) {
loaderIsRunningAsync = true
syncMatchContext(inner, matchId, index)
;(async () => {
try {
await runLoader(inner, matchPromises, matchId, index, route)
Expand Down
Loading