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
52 changes: 52 additions & 0 deletions e2e/react-router/basic-file-based/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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: '/',
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1127,6 +1144,7 @@ export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/context-propagation'
| '/fullpath-test'
| '/non-nested'
| '/pathless-layout'
Expand Down Expand Up @@ -1158,6 +1176,7 @@ export interface FileRouteTypes {
| '/redirect/$target'
| '/search-params/default'
| '/structural-sharing/$enabled'
| '/context-propagation/'
| '/params-ps/'
| '/posts/'
| '/redirect/'
Expand Down Expand Up @@ -1272,6 +1291,7 @@ export interface FileRouteTypes {
| '/posts/$postId'
| '/search-params/default'
| '/structural-sharing/$enabled'
| '/context-propagation'
| '/params-ps'
| '/posts'
| '/redirect'
Expand Down Expand Up @@ -1349,6 +1369,7 @@ export interface FileRouteTypes {
id:
| '__root__'
| '/'
| '/context-propagation'
| '/fullpath-test'
| '/non-nested'
| '/pathless-layout'
Expand Down Expand Up @@ -1385,6 +1406,7 @@ export interface FileRouteTypes {
| '/redirect/$target'
| '/search-params/default'
| '/structural-sharing/$enabled'
| '/context-propagation/'
| '/params-ps/'
| '/posts/'
| '/redirect/'
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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: '/'
Expand Down Expand Up @@ -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'
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -2869,6 +2920,7 @@ const ParamsPsNamedFooRouteRouteWithChildren =

const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
ContextPropagationRouteRoute: ContextPropagationRouteRouteWithChildren,
FullpathTestRouteRoute: FullpathTestRouteRouteWithChildren,
NonNestedRouteRoute: NonNestedRouteRouteWithChildren,
PathlessLayoutRouteRoute: PathlessLayoutRouteRouteWithChildren,
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<p data-testid="context-propagation-result">
number = {String(number)}, saw undefined = {String(sawUndefinedContext)}
</p>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/context-propagation')({
beforeLoad: () => ({ number: 42 }),
})
Original file line number Diff line number Diff line change
@@ -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',
)
})
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