From 15841e3d2c9e8417b9f2e8b721826b4a0b744b45 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 16 Oct 2025 10:47:50 +0200 Subject: [PATCH 1/8] fix: the top level Matches component should use the pendingComponent defined on the __root__ route fixes #2317 --- packages/react-router/src/Matches.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/react-router/src/Matches.tsx b/packages/react-router/src/Matches.tsx index c75fb19b8e0..3198bcebbc7 100644 --- a/packages/react-router/src/Matches.tsx +++ b/packages/react-router/src/Matches.tsx @@ -12,6 +12,7 @@ import type { ValidateSelected, } from './structuralSharing' import type { + AnyRoute, AnyRouter, DeepPartial, Expand, @@ -41,10 +42,12 @@ declare module '@tanstack/router-core' { export function Matches() { const router = useRouter() + const rootRoute: AnyRoute = router.routesById['__root__'] - const pendingElement = router.options.defaultPendingComponent ? ( - - ) : null + const PendingComponent = + rootRoute.options.pendingComponent ?? router.options.defaultPendingComponent + + const pendingElement = PendingComponent ? : null // Do not render a root Suspense during SSR or hydrating from SSR const ResolvedSuspense = From dd78c82cadcdc5fa15c56484493bdeccbfbdc909 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 16 Oct 2025 11:03:59 +0200 Subject: [PATCH 2/8] fix for solid --- packages/solid-router/src/Matches.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/solid-router/src/Matches.tsx b/packages/solid-router/src/Matches.tsx index f9fa1d34f7a..e890815b7e8 100644 --- a/packages/solid-router/src/Matches.tsx +++ b/packages/solid-router/src/Matches.tsx @@ -1,5 +1,6 @@ import * as Solid from 'solid-js' import warning from 'tiny-warning' +import * as React from 'react' import { CatchBoundary, ErrorComponent } from './CatchBoundary' import { useRouterState } from './useRouterState' import { useRouter } from './useRouter' @@ -8,6 +9,7 @@ import { matchContext } from './matchContext' import { Match } from './Match' import { SafeFragment } from './SafeFragment' import type { + AnyRoute, AnyRouter, DeepPartial, Expand, @@ -44,16 +46,17 @@ export function Matches() { ? SafeFragment : Solid.Suspense + const rootRoute: () => AnyRoute = () => router.routesById['__root__'] + const PendingComponent = + rootRoute().options.pendingComponent ?? + router.options.defaultPendingComponent + const OptionalWrapper = router.options.InnerWrap || SafeFragment return ( - ) : null - } + fallback={PendingComponent ? : null} > {!router.isServer && } From 692fed20ba046aa9eec7f2623b4e9a830e51eaa2 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 16 Oct 2025 11:16:28 +0200 Subject: [PATCH 3/8] tests --- packages/react-router/tests/Matches.test.tsx | 24 ++++++++++++++++++++ packages/solid-router/tests/Matches.test.tsx | 24 ++++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/packages/react-router/tests/Matches.test.tsx b/packages/react-router/tests/Matches.test.tsx index 61fb2461bd7..50c6551fe2c 100644 --- a/packages/react-router/tests/Matches.test.tsx +++ b/packages/react-router/tests/Matches.test.tsx @@ -121,3 +121,27 @@ test('when filtering useMatches by loaderData', async () => { expect(await screen.findByText('Incorrect Matches -')).toBeInTheDocument() }) + +test('should show pendingComponent of root route', async () => { + const root = createRootRoute({ + pendingComponent: () =>
root pending...
, + loader: async () => { + await new Promise((r) => setTimeout(r, 50)) + }, + }) + const index = createRoute({ + getParentRoute: () => root, + path: '/', + component: () =>
index route
, + }) + const router = createRouter({ + routeTree: root.addChildren([index]), + defaultPendingMs: 0, + defaultPendingComponent: () =>
default pending...
, + }) + + render() + + expect(await screen.findByText('root pending...')).toBeInTheDocument() + expect(await screen.findByText('index route')).toBeInTheDocument() +}) diff --git a/packages/solid-router/tests/Matches.test.tsx b/packages/solid-router/tests/Matches.test.tsx index 6f0f865a1bd..5a551afb22b 100644 --- a/packages/solid-router/tests/Matches.test.tsx +++ b/packages/solid-router/tests/Matches.test.tsx @@ -221,3 +221,27 @@ test('Matches provides InnerWrap context to defaultPendingComponent', async () = const indexElem = await app.findByText('context-for-default-pending') expect(indexElem).toBeInTheDocument() }) + +test('should show pendingComponent of root route', async () => { + const root = createRootRoute({ + pendingComponent: () =>
root pending...
, + loader: async () => { + await new Promise((r) => setTimeout(r, 50)) + }, + }) + const index = createRoute({ + getParentRoute: () => root, + path: '/', + component: () =>
index route
, + }) + const router = createRouter({ + routeTree: root.addChildren([index]), + defaultPendingMs: 0, + defaultPendingComponent: () =>
default pending...
, + }) + + render(() => ) + + expect(await screen.findByText('root pending...')).toBeInTheDocument() + expect(await screen.findByText('index route')).toBeInTheDocument() +}) From 955e61a76e3e5627eb42b2ab59ae169bdb4bffb9 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 16 Oct 2025 11:17:01 +0200 Subject: [PATCH 4/8] chore: auto-import --- packages/solid-router/src/Matches.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/solid-router/src/Matches.tsx b/packages/solid-router/src/Matches.tsx index e890815b7e8..f0cab407ce7 100644 --- a/packages/solid-router/src/Matches.tsx +++ b/packages/solid-router/src/Matches.tsx @@ -1,6 +1,5 @@ import * as Solid from 'solid-js' import warning from 'tiny-warning' -import * as React from 'react' import { CatchBoundary, ErrorComponent } from './CatchBoundary' import { useRouterState } from './useRouterState' import { useRouter } from './useRouter' From 051390a0f82497984b4ae79e2eb93e72fcbeb0e5 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 16 Oct 2025 11:26:29 +0200 Subject: [PATCH 5/8] chore: scope tests --- packages/react-router/tests/Matches.test.tsx | 6 +++--- packages/solid-router/tests/Matches.test.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/react-router/tests/Matches.test.tsx b/packages/react-router/tests/Matches.test.tsx index 50c6551fe2c..07879bc1611 100644 --- a/packages/react-router/tests/Matches.test.tsx +++ b/packages/react-router/tests/Matches.test.tsx @@ -140,8 +140,8 @@ test('should show pendingComponent of root route', async () => { defaultPendingComponent: () =>
default pending...
, }) - render() + const rendered = render() - expect(await screen.findByText('root pending...')).toBeInTheDocument() - expect(await screen.findByText('index route')).toBeInTheDocument() + expect(await rendered.findByText('root pending...')).toBeInTheDocument() + expect(await rendered.findByText('index route')).toBeInTheDocument() }) diff --git a/packages/solid-router/tests/Matches.test.tsx b/packages/solid-router/tests/Matches.test.tsx index 5a551afb22b..1a8e6e52dd7 100644 --- a/packages/solid-router/tests/Matches.test.tsx +++ b/packages/solid-router/tests/Matches.test.tsx @@ -240,8 +240,8 @@ test('should show pendingComponent of root route', async () => { defaultPendingComponent: () =>
default pending...
, }) - render(() => ) + const rendered = render(() => ) - expect(await screen.findByText('root pending...')).toBeInTheDocument() - expect(await screen.findByText('index route')).toBeInTheDocument() + expect(await rendered.findByText('root pending...')).toBeInTheDocument() + expect(await rendered.findByText('index route')).toBeInTheDocument() }) From 13d45c13b58ab7e358f2d94910f6c26ca1c85ce1 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Thu, 16 Oct 2025 11:38:14 +0200 Subject: [PATCH 6/8] maybe like this? --- packages/react-router/tests/Matches.test.tsx | 10 +++------- packages/solid-router/tests/Matches.test.tsx | 11 ++++------- 2 files changed, 7 insertions(+), 14 deletions(-) diff --git a/packages/react-router/tests/Matches.test.tsx b/packages/react-router/tests/Matches.test.tsx index 07879bc1611..0d6eb17597b 100644 --- a/packages/react-router/tests/Matches.test.tsx +++ b/packages/react-router/tests/Matches.test.tsx @@ -128,14 +128,10 @@ test('should show pendingComponent of root route', async () => { loader: async () => { await new Promise((r) => setTimeout(r, 50)) }, - }) - const index = createRoute({ - getParentRoute: () => root, - path: '/', - component: () =>
index route
, + component: () => 'root content', }) const router = createRouter({ - routeTree: root.addChildren([index]), + routeTree: root, defaultPendingMs: 0, defaultPendingComponent: () =>
default pending...
, }) @@ -143,5 +139,5 @@ test('should show pendingComponent of root route', async () => { const rendered = render() expect(await rendered.findByText('root pending...')).toBeInTheDocument() - expect(await rendered.findByText('index route')).toBeInTheDocument() + expect(await rendered.findByText('root content')).toBeInTheDocument() }) diff --git a/packages/solid-router/tests/Matches.test.tsx b/packages/solid-router/tests/Matches.test.tsx index 1a8e6e52dd7..418599ae655 100644 --- a/packages/solid-router/tests/Matches.test.tsx +++ b/packages/solid-router/tests/Matches.test.tsx @@ -228,14 +228,11 @@ test('should show pendingComponent of root route', async () => { loader: async () => { await new Promise((r) => setTimeout(r, 50)) }, + component: () => 'root content', }) - const index = createRoute({ - getParentRoute: () => root, - path: '/', - component: () =>
index route
, - }) + const router = createRouter({ - routeTree: root.addChildren([index]), + routeTree: root, defaultPendingMs: 0, defaultPendingComponent: () =>
default pending...
, }) @@ -243,5 +240,5 @@ test('should show pendingComponent of root route', async () => { const rendered = render(() => ) expect(await rendered.findByText('root pending...')).toBeInTheDocument() - expect(await rendered.findByText('index route')).toBeInTheDocument() + expect(await rendered.findByText('root content')).toBeInTheDocument() }) From 74a3835026e8d532ae6d1596c403dba95337b028 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Fri, 17 Oct 2025 08:36:11 +0200 Subject: [PATCH 7/8] ref: use rootRouteId --- packages/react-router/src/Matches.tsx | 3 ++- packages/solid-router/src/Matches.tsx | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/react-router/src/Matches.tsx b/packages/react-router/src/Matches.tsx index 3198bcebbc7..1b7ec2cc6bc 100644 --- a/packages/react-router/src/Matches.tsx +++ b/packages/react-router/src/Matches.tsx @@ -1,5 +1,6 @@ import * as React from 'react' import warning from 'tiny-warning' +import { rootRouteId } from '@tanstack/router-core' import { CatchBoundary, ErrorComponent } from './CatchBoundary' import { useRouterState } from './useRouterState' import { useRouter } from './useRouter' @@ -42,7 +43,7 @@ declare module '@tanstack/router-core' { export function Matches() { const router = useRouter() - const rootRoute: AnyRoute = router.routesById['__root__'] + const rootRoute: AnyRoute = router.routesById[rootRouteId] const PendingComponent = rootRoute.options.pendingComponent ?? router.options.defaultPendingComponent diff --git a/packages/solid-router/src/Matches.tsx b/packages/solid-router/src/Matches.tsx index f0cab407ce7..52bf981963d 100644 --- a/packages/solid-router/src/Matches.tsx +++ b/packages/solid-router/src/Matches.tsx @@ -1,5 +1,6 @@ import * as Solid from 'solid-js' import warning from 'tiny-warning' +import { rootRouteId } from '@tanstack/router-core' import { CatchBoundary, ErrorComponent } from './CatchBoundary' import { useRouterState } from './useRouterState' import { useRouter } from './useRouter' @@ -45,7 +46,7 @@ export function Matches() { ? SafeFragment : Solid.Suspense - const rootRoute: () => AnyRoute = () => router.routesById['__root__'] + const rootRoute: () => AnyRoute = () => router.routesById[rootRouteId] const PendingComponent = rootRoute().options.pendingComponent ?? router.options.defaultPendingComponent From 6fc6e2b6af4efabd21aa073bc5ad94800bd76c46 Mon Sep 17 00:00:00 2001 From: TkDodo Date: Fri, 17 Oct 2025 08:38:47 +0200 Subject: [PATCH 8/8] ref: switch to data-testId --- packages/react-router/tests/Matches.test.tsx | 8 ++++---- packages/solid-router/tests/Matches.test.tsx | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/react-router/tests/Matches.test.tsx b/packages/react-router/tests/Matches.test.tsx index 0d6eb17597b..3cb4f5c2b73 100644 --- a/packages/react-router/tests/Matches.test.tsx +++ b/packages/react-router/tests/Matches.test.tsx @@ -124,11 +124,11 @@ test('when filtering useMatches by loaderData', async () => { test('should show pendingComponent of root route', async () => { const root = createRootRoute({ - pendingComponent: () =>
root pending...
, + pendingComponent: () =>
, loader: async () => { await new Promise((r) => setTimeout(r, 50)) }, - component: () => 'root content', + component: () =>
, }) const router = createRouter({ routeTree: root, @@ -138,6 +138,6 @@ test('should show pendingComponent of root route', async () => { const rendered = render() - expect(await rendered.findByText('root pending...')).toBeInTheDocument() - expect(await rendered.findByText('root content')).toBeInTheDocument() + expect(await rendered.findByTestId('root-pending')).toBeInTheDocument() + expect(await rendered.findByTestId('root-content')).toBeInTheDocument() }) diff --git a/packages/solid-router/tests/Matches.test.tsx b/packages/solid-router/tests/Matches.test.tsx index 418599ae655..aed930bb0d8 100644 --- a/packages/solid-router/tests/Matches.test.tsx +++ b/packages/solid-router/tests/Matches.test.tsx @@ -224,11 +224,11 @@ test('Matches provides InnerWrap context to defaultPendingComponent', async () = test('should show pendingComponent of root route', async () => { const root = createRootRoute({ - pendingComponent: () =>
root pending...
, + pendingComponent: () =>
, loader: async () => { await new Promise((r) => setTimeout(r, 50)) }, - component: () => 'root content', + component: () =>
, }) const router = createRouter({ @@ -239,6 +239,6 @@ test('should show pendingComponent of root route', async () => { const rendered = render(() => ) - expect(await rendered.findByText('root pending...')).toBeInTheDocument() - expect(await rendered.findByText('root content')).toBeInTheDocument() + expect(await rendered.findByTestId('root-pending')).toBeInTheDocument() + expect(await rendered.findByTestId('root-content')).toBeInTheDocument() })