From f04a75caca216034537426052833149426bfce05 Mon Sep 17 00:00:00 2001 From: Ulrich Stark <8657779+ulrichstark@users.noreply.github.com> Date: Thu, 11 Jun 2026 16:02:49 +0200 Subject: [PATCH 1/2] fix(router-core): propagate beforeLoad context to sub-route while its loader reloads in the background --- .changeset/violet-poets-wait.md | 5 ++ .../basic-file-based/src/routeTree.gen.ts | 52 +++++++++++++++++++ .../src/routes/context-propagation/index.tsx | 23 ++++++++ .../src/routes/context-propagation/route.tsx | 5 ++ .../tests/context-propagation.spec.ts | 22 ++++++++ packages/router-core/src/load-matches.ts | 1 + 6 files changed, 108 insertions(+) create mode 100644 .changeset/violet-poets-wait.md create mode 100644 e2e/react-router/basic-file-based/src/routes/context-propagation/index.tsx create mode 100644 e2e/react-router/basic-file-based/src/routes/context-propagation/route.tsx create mode 100644 e2e/react-router/basic-file-based/tests/context-propagation.spec.ts diff --git a/.changeset/violet-poets-wait.md b/.changeset/violet-poets-wait.md new file mode 100644 index 0000000000..8d86936fec --- /dev/null +++ b/.changeset/violet-poets-wait.md @@ -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 diff --git a/e2e/react-router/basic-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-file-based/src/routeTree.gen.ts index 7ee0051549..2a5d84fb94 100644 --- a/e2e/react-router/basic-file-based/src/routeTree.gen.ts +++ b/e2e/react-router/basic-file-based/src/routeTree.gen.ts @@ -25,12 +25,14 @@ import { Route as SearchParamsRouteRouteImport } from './routes/search-params/ro import { Route as PathlessLayoutRouteRouteImport } from './routes/pathless-layout/route' import { Route as NonNestedRouteRouteImport } from './routes/non-nested/route' import { Route as FullpathTestRouteRouteImport } from './routes/fullpath-test/route' +import { Route as ContextPropagationRouteRouteImport } from './routes/context-propagation/route' import { Route as IndexRouteImport } from './routes/index' import { Route as SearchParamsIndexRouteImport } from './routes/search-params/index' import { Route as RelativeIndexRouteImport } from './routes/relative/index' import { Route as RedirectIndexRouteImport } from './routes/redirect/index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as ParamsPsIndexRouteImport } from './routes/params-ps/index' +import { Route as ContextPropagationIndexRouteImport } from './routes/context-propagation/index' import { Route as StructuralSharingEnabledRouteImport } from './routes/structural-sharing.$enabled' import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' @@ -210,6 +212,11 @@ const FullpathTestRouteRoute = FullpathTestRouteRouteImport.update({ path: '/fullpath-test', getParentRoute: () => rootRouteImport, } as any) +const ContextPropagationRouteRoute = ContextPropagationRouteRouteImport.update({ + id: '/context-propagation', + path: '/context-propagation', + getParentRoute: () => rootRouteImport, +} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', @@ -240,6 +247,11 @@ const ParamsPsIndexRoute = ParamsPsIndexRouteImport.update({ path: '/params-ps/', getParentRoute: () => rootRouteImport, } as any) +const ContextPropagationIndexRoute = ContextPropagationIndexRouteImport.update({ + id: '/', + path: '/', + getParentRoute: () => ContextPropagationRouteRoute, +} as any) const StructuralSharingEnabledRoute = StructuralSharingEnabledRouteImport.update({ id: '/structural-sharing/$enabled', @@ -780,6 +792,7 @@ const NonNestedDeepBazBarFooQuxRoute = export interface FileRoutesByFullPath { '/': typeof IndexRoute + '/context-propagation': typeof ContextPropagationRouteRouteWithChildren '/fullpath-test': typeof FullpathTestRouteRouteWithChildren '/non-nested': typeof NonNestedRouteRouteWithChildren '/pathless-layout': typeof PathlessLayoutRouteRouteWithChildren @@ -811,6 +824,7 @@ export interface FileRoutesByFullPath { '/redirect/$target': typeof RedirectTargetRouteWithChildren '/search-params/default': typeof SearchParamsDefaultRoute '/structural-sharing/$enabled': typeof StructuralSharingEnabledRoute + '/context-propagation/': typeof ContextPropagationIndexRoute '/params-ps/': typeof ParamsPsIndexRoute '/posts/': typeof PostsIndexRoute '/redirect/': typeof RedirectIndexRoute @@ -925,6 +939,7 @@ export interface FileRoutesByTo { '/posts/$postId': typeof PostsPostIdRoute '/search-params/default': typeof SearchParamsDefaultRoute '/structural-sharing/$enabled': typeof StructuralSharingEnabledRoute + '/context-propagation': typeof ContextPropagationIndexRoute '/params-ps': typeof ParamsPsIndexRoute '/posts': typeof PostsIndexRoute '/redirect': typeof RedirectIndexRoute @@ -1003,6 +1018,7 @@ export interface FileRoutesByTo { export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute + '/context-propagation': typeof ContextPropagationRouteRouteWithChildren '/fullpath-test': typeof FullpathTestRouteRouteWithChildren '/non-nested': typeof NonNestedRouteRouteWithChildren '/pathless-layout': typeof PathlessLayoutRouteRouteWithChildren @@ -1039,6 +1055,7 @@ export interface FileRoutesById { '/redirect/$target': typeof RedirectTargetRouteWithChildren '/search-params/default': typeof SearchParamsDefaultRoute '/structural-sharing/$enabled': typeof StructuralSharingEnabledRoute + '/context-propagation/': typeof ContextPropagationIndexRoute '/params-ps/': typeof ParamsPsIndexRoute '/posts/': typeof PostsIndexRoute '/redirect/': typeof RedirectIndexRoute @@ -1127,6 +1144,7 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' + | '/context-propagation' | '/fullpath-test' | '/non-nested' | '/pathless-layout' @@ -1158,6 +1176,7 @@ export interface FileRouteTypes { | '/redirect/$target' | '/search-params/default' | '/structural-sharing/$enabled' + | '/context-propagation/' | '/params-ps/' | '/posts/' | '/redirect/' @@ -1272,6 +1291,7 @@ export interface FileRouteTypes { | '/posts/$postId' | '/search-params/default' | '/structural-sharing/$enabled' + | '/context-propagation' | '/params-ps' | '/posts' | '/redirect' @@ -1349,6 +1369,7 @@ export interface FileRouteTypes { id: | '__root__' | '/' + | '/context-propagation' | '/fullpath-test' | '/non-nested' | '/pathless-layout' @@ -1385,6 +1406,7 @@ export interface FileRouteTypes { | '/redirect/$target' | '/search-params/default' | '/structural-sharing/$enabled' + | '/context-propagation/' | '/params-ps/' | '/posts/' | '/redirect/' @@ -1472,6 +1494,7 @@ export interface FileRouteTypes { } export interface RootRouteChildren { IndexRoute: typeof IndexRoute + ContextPropagationRouteRoute: typeof ContextPropagationRouteRouteWithChildren FullpathTestRouteRoute: typeof FullpathTestRouteRouteWithChildren NonNestedRouteRoute: typeof NonNestedRouteRouteWithChildren PathlessLayoutRouteRoute: typeof PathlessLayoutRouteRouteWithChildren @@ -1633,6 +1656,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof FullpathTestRouteRouteImport parentRoute: typeof rootRouteImport } + '/context-propagation': { + id: '/context-propagation' + path: '/context-propagation' + fullPath: '/context-propagation' + preLoaderRoute: typeof ContextPropagationRouteRouteImport + parentRoute: typeof rootRouteImport + } '/': { id: '/' path: '/' @@ -1675,6 +1705,13 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ParamsPsIndexRouteImport parentRoute: typeof rootRouteImport } + '/context-propagation/': { + id: '/context-propagation/' + path: '/' + fullPath: '/context-propagation/' + preLoaderRoute: typeof ContextPropagationIndexRouteImport + parentRoute: typeof ContextPropagationRouteRoute + } '/structural-sharing/$enabled': { id: '/structural-sharing/$enabled' path: '/structural-sharing/$enabled' @@ -2364,6 +2401,20 @@ declare module '@tanstack/react-router' { } } +interface ContextPropagationRouteRouteChildren { + ContextPropagationIndexRoute: typeof ContextPropagationIndexRoute +} + +const ContextPropagationRouteRouteChildren: ContextPropagationRouteRouteChildren = + { + ContextPropagationIndexRoute: ContextPropagationIndexRoute, + } + +const ContextPropagationRouteRouteWithChildren = + ContextPropagationRouteRoute._addFileChildren( + ContextPropagationRouteRouteChildren, + ) + interface FullpathTestLayoutRouteRouteChildren { FullpathTestLayoutIdRoute: typeof FullpathTestLayoutIdRoute FullpathTestLayoutIndexRoute: typeof FullpathTestLayoutIndexRoute @@ -2869,6 +2920,7 @@ const ParamsPsNamedFooRouteRouteWithChildren = const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, + ContextPropagationRouteRoute: ContextPropagationRouteRouteWithChildren, FullpathTestRouteRoute: FullpathTestRouteRouteWithChildren, NonNestedRouteRoute: NonNestedRouteRouteWithChildren, PathlessLayoutRouteRoute: PathlessLayoutRouteRouteWithChildren, diff --git a/e2e/react-router/basic-file-based/src/routes/context-propagation/index.tsx b/e2e/react-router/basic-file-based/src/routes/context-propagation/index.tsx new file mode 100644 index 0000000000..a8b5e3a029 --- /dev/null +++ b/e2e/react-router/basic-file-based/src/routes/context-propagation/index.tsx @@ -0,0 +1,23 @@ +import { createFileRoute } from '@tanstack/react-router' + +// Records whether the component ever rendered without the context value from +// the parent route's beforeLoad (https://github.com/TanStack/router/issues/7602) +let sawUndefinedContext = false + +export const Route = createFileRoute('/context-propagation/')({ + // ensure the loader runs again on back navigation despite defaultStaleTime + staleTime: 0, + loader: () => new Promise((resolve) => setTimeout(resolve, 100)), + component: RouteComponent, +}) + +function RouteComponent() { + const { number } = Route.useRouteContext() + sawUndefinedContext ||= number === undefined + + return ( +

+ number = {String(number)}, saw undefined = {String(sawUndefinedContext)} +

+ ) +} diff --git a/e2e/react-router/basic-file-based/src/routes/context-propagation/route.tsx b/e2e/react-router/basic-file-based/src/routes/context-propagation/route.tsx new file mode 100644 index 0000000000..cc04221d5b --- /dev/null +++ b/e2e/react-router/basic-file-based/src/routes/context-propagation/route.tsx @@ -0,0 +1,5 @@ +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/context-propagation')({ + beforeLoad: () => ({ number: 42 }), +}) diff --git a/e2e/react-router/basic-file-based/tests/context-propagation.spec.ts b/e2e/react-router/basic-file-based/tests/context-propagation.spec.ts new file mode 100644 index 0000000000..4729b497c3 --- /dev/null +++ b/e2e/react-router/basic-file-based/tests/context-propagation.spec.ts @@ -0,0 +1,22 @@ +import { expect, test } from '@playwright/test' + +// https://github.com/TanStack/router/issues/7602 +test('context value from beforeLoad is propagated to a sub-route while the loader of the sub-route is executing', async ({ + page, +}) => { + await page.goto('/context-propagation') + await expect(page.getByTestId('context-propagation-result')).toHaveText( + 'number = 42, saw undefined = false', + ) + + await page.getByRole('link', { name: 'Home', exact: true }).click() + await expect(page.getByRole('heading')).toContainText('Welcome Home!') + + await page.goBack() + + // the component must never render with an undefined context value + // while the loader is executing + await expect(page.getByTestId('context-propagation-result')).toHaveText( + 'number = 42, saw undefined = false', + ) +}) diff --git a/packages/router-core/src/load-matches.ts b/packages/router-core/src/load-matches.ts index f901a0c97d..20d31afd1d 100644 --- a/packages/router-core/src/load-matches.ts +++ b/packages/router-core/src/load-matches.ts @@ -832,6 +832,7 @@ const loadRouteMatch = async ( shouldReloadInBackground ) { loaderIsRunningAsync = true + syncMatchContext(inner, matchId, index) ;(async () => { try { await runLoader(inner, matchPromises, matchId, index, route) From 92da7bf07f29ff87ec0e3a89608292ad75c3fee1 Mon Sep 17 00:00:00 2001 From: Ulrich Stark <8657779+ulrichstark@users.noreply.github.com> Date: Sat, 13 Jun 2026 00:06:22 +0200 Subject: [PATCH 2/2] replace e2e test with unit test --- .../basic-file-based/src/routeTree.gen.ts | 52 ---------------- .../src/routes/context-propagation/index.tsx | 23 ------- .../src/routes/context-propagation/route.tsx | 5 -- .../tests/context-propagation.spec.ts | 22 ------- .../react-router/tests/routeContext.test.tsx | 62 +++++++++++++++++++ 5 files changed, 62 insertions(+), 102 deletions(-) delete mode 100644 e2e/react-router/basic-file-based/src/routes/context-propagation/index.tsx delete mode 100644 e2e/react-router/basic-file-based/src/routes/context-propagation/route.tsx delete mode 100644 e2e/react-router/basic-file-based/tests/context-propagation.spec.ts diff --git a/e2e/react-router/basic-file-based/src/routeTree.gen.ts b/e2e/react-router/basic-file-based/src/routeTree.gen.ts index 2a5d84fb94..7ee0051549 100644 --- a/e2e/react-router/basic-file-based/src/routeTree.gen.ts +++ b/e2e/react-router/basic-file-based/src/routeTree.gen.ts @@ -25,14 +25,12 @@ import { Route as SearchParamsRouteRouteImport } from './routes/search-params/ro import { Route as PathlessLayoutRouteRouteImport } from './routes/pathless-layout/route' import { Route as NonNestedRouteRouteImport } from './routes/non-nested/route' import { Route as FullpathTestRouteRouteImport } from './routes/fullpath-test/route' -import { Route as ContextPropagationRouteRouteImport } from './routes/context-propagation/route' import { Route as IndexRouteImport } from './routes/index' import { Route as SearchParamsIndexRouteImport } from './routes/search-params/index' import { Route as RelativeIndexRouteImport } from './routes/relative/index' import { Route as RedirectIndexRouteImport } from './routes/redirect/index' import { Route as PostsIndexRouteImport } from './routes/posts.index' import { Route as ParamsPsIndexRouteImport } from './routes/params-ps/index' -import { Route as ContextPropagationIndexRouteImport } from './routes/context-propagation/index' import { Route as StructuralSharingEnabledRouteImport } from './routes/structural-sharing.$enabled' import { Route as SearchParamsDefaultRouteImport } from './routes/search-params/default' import { Route as RedirectTargetRouteImport } from './routes/redirect/$target' @@ -212,11 +210,6 @@ const FullpathTestRouteRoute = FullpathTestRouteRouteImport.update({ path: '/fullpath-test', getParentRoute: () => rootRouteImport, } as any) -const ContextPropagationRouteRoute = ContextPropagationRouteRouteImport.update({ - id: '/context-propagation', - path: '/context-propagation', - getParentRoute: () => rootRouteImport, -} as any) const IndexRoute = IndexRouteImport.update({ id: '/', path: '/', @@ -247,11 +240,6 @@ const ParamsPsIndexRoute = ParamsPsIndexRouteImport.update({ path: '/params-ps/', getParentRoute: () => rootRouteImport, } as any) -const ContextPropagationIndexRoute = ContextPropagationIndexRouteImport.update({ - id: '/', - path: '/', - getParentRoute: () => ContextPropagationRouteRoute, -} as any) const StructuralSharingEnabledRoute = StructuralSharingEnabledRouteImport.update({ id: '/structural-sharing/$enabled', @@ -792,7 +780,6 @@ const NonNestedDeepBazBarFooQuxRoute = export interface FileRoutesByFullPath { '/': typeof IndexRoute - '/context-propagation': typeof ContextPropagationRouteRouteWithChildren '/fullpath-test': typeof FullpathTestRouteRouteWithChildren '/non-nested': typeof NonNestedRouteRouteWithChildren '/pathless-layout': typeof PathlessLayoutRouteRouteWithChildren @@ -824,7 +811,6 @@ export interface FileRoutesByFullPath { '/redirect/$target': typeof RedirectTargetRouteWithChildren '/search-params/default': typeof SearchParamsDefaultRoute '/structural-sharing/$enabled': typeof StructuralSharingEnabledRoute - '/context-propagation/': typeof ContextPropagationIndexRoute '/params-ps/': typeof ParamsPsIndexRoute '/posts/': typeof PostsIndexRoute '/redirect/': typeof RedirectIndexRoute @@ -939,7 +925,6 @@ export interface FileRoutesByTo { '/posts/$postId': typeof PostsPostIdRoute '/search-params/default': typeof SearchParamsDefaultRoute '/structural-sharing/$enabled': typeof StructuralSharingEnabledRoute - '/context-propagation': typeof ContextPropagationIndexRoute '/params-ps': typeof ParamsPsIndexRoute '/posts': typeof PostsIndexRoute '/redirect': typeof RedirectIndexRoute @@ -1018,7 +1003,6 @@ export interface FileRoutesByTo { export interface FileRoutesById { __root__: typeof rootRouteImport '/': typeof IndexRoute - '/context-propagation': typeof ContextPropagationRouteRouteWithChildren '/fullpath-test': typeof FullpathTestRouteRouteWithChildren '/non-nested': typeof NonNestedRouteRouteWithChildren '/pathless-layout': typeof PathlessLayoutRouteRouteWithChildren @@ -1055,7 +1039,6 @@ export interface FileRoutesById { '/redirect/$target': typeof RedirectTargetRouteWithChildren '/search-params/default': typeof SearchParamsDefaultRoute '/structural-sharing/$enabled': typeof StructuralSharingEnabledRoute - '/context-propagation/': typeof ContextPropagationIndexRoute '/params-ps/': typeof ParamsPsIndexRoute '/posts/': typeof PostsIndexRoute '/redirect/': typeof RedirectIndexRoute @@ -1144,7 +1127,6 @@ export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath fullPaths: | '/' - | '/context-propagation' | '/fullpath-test' | '/non-nested' | '/pathless-layout' @@ -1176,7 +1158,6 @@ export interface FileRouteTypes { | '/redirect/$target' | '/search-params/default' | '/structural-sharing/$enabled' - | '/context-propagation/' | '/params-ps/' | '/posts/' | '/redirect/' @@ -1291,7 +1272,6 @@ export interface FileRouteTypes { | '/posts/$postId' | '/search-params/default' | '/structural-sharing/$enabled' - | '/context-propagation' | '/params-ps' | '/posts' | '/redirect' @@ -1369,7 +1349,6 @@ export interface FileRouteTypes { id: | '__root__' | '/' - | '/context-propagation' | '/fullpath-test' | '/non-nested' | '/pathless-layout' @@ -1406,7 +1385,6 @@ export interface FileRouteTypes { | '/redirect/$target' | '/search-params/default' | '/structural-sharing/$enabled' - | '/context-propagation/' | '/params-ps/' | '/posts/' | '/redirect/' @@ -1494,7 +1472,6 @@ export interface FileRouteTypes { } export interface RootRouteChildren { IndexRoute: typeof IndexRoute - ContextPropagationRouteRoute: typeof ContextPropagationRouteRouteWithChildren FullpathTestRouteRoute: typeof FullpathTestRouteRouteWithChildren NonNestedRouteRoute: typeof NonNestedRouteRouteWithChildren PathlessLayoutRouteRoute: typeof PathlessLayoutRouteRouteWithChildren @@ -1656,13 +1633,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof FullpathTestRouteRouteImport parentRoute: typeof rootRouteImport } - '/context-propagation': { - id: '/context-propagation' - path: '/context-propagation' - fullPath: '/context-propagation' - preLoaderRoute: typeof ContextPropagationRouteRouteImport - parentRoute: typeof rootRouteImport - } '/': { id: '/' path: '/' @@ -1705,13 +1675,6 @@ declare module '@tanstack/react-router' { preLoaderRoute: typeof ParamsPsIndexRouteImport parentRoute: typeof rootRouteImport } - '/context-propagation/': { - id: '/context-propagation/' - path: '/' - fullPath: '/context-propagation/' - preLoaderRoute: typeof ContextPropagationIndexRouteImport - parentRoute: typeof ContextPropagationRouteRoute - } '/structural-sharing/$enabled': { id: '/structural-sharing/$enabled' path: '/structural-sharing/$enabled' @@ -2401,20 +2364,6 @@ declare module '@tanstack/react-router' { } } -interface ContextPropagationRouteRouteChildren { - ContextPropagationIndexRoute: typeof ContextPropagationIndexRoute -} - -const ContextPropagationRouteRouteChildren: ContextPropagationRouteRouteChildren = - { - ContextPropagationIndexRoute: ContextPropagationIndexRoute, - } - -const ContextPropagationRouteRouteWithChildren = - ContextPropagationRouteRoute._addFileChildren( - ContextPropagationRouteRouteChildren, - ) - interface FullpathTestLayoutRouteRouteChildren { FullpathTestLayoutIdRoute: typeof FullpathTestLayoutIdRoute FullpathTestLayoutIndexRoute: typeof FullpathTestLayoutIndexRoute @@ -2920,7 +2869,6 @@ const ParamsPsNamedFooRouteRouteWithChildren = const rootRouteChildren: RootRouteChildren = { IndexRoute: IndexRoute, - ContextPropagationRouteRoute: ContextPropagationRouteRouteWithChildren, FullpathTestRouteRoute: FullpathTestRouteRouteWithChildren, NonNestedRouteRoute: NonNestedRouteRouteWithChildren, PathlessLayoutRouteRoute: PathlessLayoutRouteRouteWithChildren, diff --git a/e2e/react-router/basic-file-based/src/routes/context-propagation/index.tsx b/e2e/react-router/basic-file-based/src/routes/context-propagation/index.tsx deleted file mode 100644 index a8b5e3a029..0000000000 --- a/e2e/react-router/basic-file-based/src/routes/context-propagation/index.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router' - -// Records whether the component ever rendered without the context value from -// the parent route's beforeLoad (https://github.com/TanStack/router/issues/7602) -let sawUndefinedContext = false - -export const Route = createFileRoute('/context-propagation/')({ - // ensure the loader runs again on back navigation despite defaultStaleTime - staleTime: 0, - loader: () => new Promise((resolve) => setTimeout(resolve, 100)), - component: RouteComponent, -}) - -function RouteComponent() { - const { number } = Route.useRouteContext() - sawUndefinedContext ||= number === undefined - - return ( -

- number = {String(number)}, saw undefined = {String(sawUndefinedContext)} -

- ) -} diff --git a/e2e/react-router/basic-file-based/src/routes/context-propagation/route.tsx b/e2e/react-router/basic-file-based/src/routes/context-propagation/route.tsx deleted file mode 100644 index cc04221d5b..0000000000 --- a/e2e/react-router/basic-file-based/src/routes/context-propagation/route.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/context-propagation')({ - beforeLoad: () => ({ number: 42 }), -}) diff --git a/e2e/react-router/basic-file-based/tests/context-propagation.spec.ts b/e2e/react-router/basic-file-based/tests/context-propagation.spec.ts deleted file mode 100644 index 4729b497c3..0000000000 --- a/e2e/react-router/basic-file-based/tests/context-propagation.spec.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { expect, test } from '@playwright/test' - -// https://github.com/TanStack/router/issues/7602 -test('context value from beforeLoad is propagated to a sub-route while the loader of the sub-route is executing', async ({ - page, -}) => { - await page.goto('/context-propagation') - await expect(page.getByTestId('context-propagation-result')).toHaveText( - 'number = 42, saw undefined = false', - ) - - await page.getByRole('link', { name: 'Home', exact: true }).click() - await expect(page.getByRole('heading')).toContainText('Welcome Home!') - - await page.goBack() - - // the component must never render with an undefined context value - // while the loader is executing - await expect(page.getByTestId('context-propagation-result')).toHaveText( - 'number = 42, saw undefined = false', - ) -}) diff --git a/packages/react-router/tests/routeContext.test.tsx b/packages/react-router/tests/routeContext.test.tsx index d11f86420e..b92f0c374f 100644 --- a/packages/react-router/tests/routeContext.test.tsx +++ b/packages/react-router/tests/routeContext.test.tsx @@ -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: () => , + }) + const homeRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () =>
Home page
, + }) + const contextPropagationRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/context-propagation', + beforeLoad: () => ({ number: 42 }), + component: () => , + }) + const contextPropagationIndexRoute = createRoute({ + getParentRoute: () => contextPropagationRoute, + path: '/', + staleTime: 0, + loader: async () => { + await sleep(WAIT_TIME) + }, + component: () => { + const { number } = contextPropagationIndexRoute.useRouteContext() + sawUndefinedContext ||= number === undefined + + return ( +
+ number = {String(number)}, saw undefined ={' '} + {String(sawUndefinedContext)} +
+ ) + }, + }) + + const routeTree = rootRoute.addChildren([ + homeRoute, + contextPropagationRoute.addChildren([contextPropagationIndexRoute]), + ]) + const router = createRouter({ routeTree, history }) + + render() + + 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() + }) + // 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({