Repository: TanStack/router
Pull request #2669 (2024-10-29 17:30:10)

fix(react-router): improve types for `parentMatchPromise`

nx-cloud[bot]: # ☁️ Nx Cloud Report

CI is running for commit ed0d85609fa7b2f3970599049d93ef7d1402f619.

[📂 Click to track the progress, see the status, the terminal output, and the build insights.](https://cloud.nx.app/cipes/67211bbf694eac316458256d?utm_source=pull-request&utm_medium=comment)


---

Sent with 💌 from [NxCloud.](https://nx.app?utm_source=pull-request&utm_medium=comment)
pkg-pr-new[bot]: 
[Open in Stackblitz](https://pkg.pr.new/template/7a57e867-f90d-437a-a362-ef6eb64a32ac)
<details><summary><b>More templates</b></summary><p>

- [tanstack-router-react-example-basic](https://pkg.pr.new/template/8c09a64a-8250-464a-9053-e74fd4227905)
- [tanstack-router-react-example-basic-default-search-params](https://pkg.pr.new/template/aac5af69-7c6b-413d-834f-a1612311f332)
- [tanstack-router-react-example-basic-file-based](https://pkg.pr.new/template/2541b6d1-7485-400f-8ffa-482f0e5f4564)
- [tanstack-router-react-example-basic-file-based-codesplitting](https://pkg.pr.new/template/7b4d17e0-9a43-4827-a138-0980e18d76cd)
- [tanstack-router-react-example-react-query](https://pkg.pr.new/template/a14464ea-91b8-4001-8158-bd7d3b9b7797)
- [tanstack-router-react-example-basic-react-query-file-based](https://pkg.pr.new/template/dcefa088-6d71-4bd4-82f4-d5861e91b2bb)
- [tanstack-router-react-example-authenticated-routes](https://pkg.pr.new/template/bccd1aa2-45bc-4364-ab97-4b04399a8342)
- [tanstack-router-react-example-basic-ssr-file-based](https://pkg.pr.new/template/bd17c82f-7ae5-4bef-9cd0-44dc9b58093b)
- [tanstack-router-react-example-basic-ssr-streaming-file-based](https://pkg.pr.new/template/3600c8a7-e231-4985-92af-f53023944bd9)
- [tanstack-router-react-example-basic-virtual-file-based](https://pkg.pr.new/template/61e2ca81-209d-4316-9d04-f653f2c4bac8)
- [tanstack-router-react-example-basic-virtual-inside-file-based](https://pkg.pr.new/template/fbca792b-5352-4ac4-9f7a-2bb297a8e931)
- [tanstack-router-react-example-deferred-data](https://pkg.pr.new/template/653a86e0-d9db-496b-b785-f35a81014bb4)
- [tanstack-router-react-example-kitchen-sink](https://pkg.pr.new/template/c77fe67e-db05-4b3c-97ef-30c41ee0fa41)
- [tanstack-router-react-example-kitchen-sink-file-based](https://pkg.pr.new/template/79db70f1-9e41-4220-bec4-d43c7a34c388)
- [tanstack-router-react-example-kitchen-sink-react-query](https://pkg.pr.new/template/616be926-5786-416e-ade0-1888cc0596fe)
- [tanstack-router-react-example-kitchen-sink-react-query-file-based](https://pkg.pr.new/template/fbdbab39-02a3-4840-8a05-b60e0aecb37b)
- [tanstack-router-react-example-large-file-based](https://pkg.pr.new/template/5d3e2138-fbbf-4cba-ab9d-37280f2b99ed)
- [tanstack-router-react-example-location-masking](https://pkg.pr.new/template/8cc44f18-b9bd-428d-9539-d35b2db557fb)
- [tanstack-router-react-example-navigation-blocking](https://pkg.pr.new/template/701638bd-6806-446f-b5a3-11dc70519c96)
- [tanstack-router-react-example-quickstart](https://pkg.pr.new/template/d69d1b8c-bfec-415c-8edc-a8fd3016c1f2)
- [tanstack-router-react-example-quickstart-esbuild-file-based](https://pkg.pr.new/template/9d3dae4c-86f5-4879-803c-fd7683bb8a56)
- [tanstack-router-react-example-quickstart-file-based](https://pkg.pr.new/template/bf803213-f8a5-4b39-ac39-2d61e05c4412)
- [tanstack-router-react-example-quickstart-rspack-file-based](https://pkg.pr.new/template/c6200162-0ed9-429d-9f2f-9676610df7da)
- [tanstack-router-react-example-quickstart-webpack-file-based](https://pkg.pr.new/template/5860f714-0416-43ca-9ac1-5c36b8e1c85d)
- [router-monorepo-react-query](https://pkg.pr.new/template/1fe2e540-9a4c-415a-9a50-1d6b4c8e7472)
- [router-mono-simple](https://pkg.pr.new/template/5c459116-d6ba-4001-bdf3-8d0d53a959e1)
- [tanstack-router-react-example-scroll-restoration](https://pkg.pr.new/template/d7b67cdf-e7d2-4d85-8c3e-c6b6d3d81f2b)
- [tanstack-router-search-validator-adapters](https://pkg.pr.new/template/84147ca8-09d8-4679-9b48-eb30890cb78f)
- [tanstack-start-example-basic](https://pkg.pr.new/template/58d17303-c9e1-4cfa-b058-d27eac23a45e)
- [tanstack-start-example-basic-auth](https://pkg.pr.new/template/00da887c-3995-4f00-a3cf-0ea2826f2234)
- [tanstack-start-example-basic-react-query](https://pkg.pr.new/template/a052cabb-a96d-453c-b586-1ead0b8171d5)
- [tanstack-start-example-basic-rsc](https://pkg.pr.new/template/782ccaaa-630c-43cd-a8b3-05299d3a84b4)
- [tanstack-start-example-clerk-basic](https://pkg.pr.new/template/7c12e3cd-e2cd-42e1-bd5e-b0e62223d2f3)
- [tanstack-start-example-convex-trellaux](https://pkg.pr.new/template/338b739d-5a31-4307-9c2d-fddd852c9132)
- [tanstack-start-example-counter](https://pkg.pr.new/template/b4f4802d-3170-4c2f-af85-495c3efe1213)
- [tanstack-start-example-supabase-basic](https://pkg.pr.new/template/8fb36e70-303a-43b2-bdd8-a1eb76751d3a)
- [tanstack-start-example-trellaux](https://pkg.pr.new/template/a1e902a7-9f30-4e28-80d6-17919d290cd4)
- [tanstack-router-react-example-with-framer-motion](https://pkg.pr.new/template/c20ade80-1fda-48ac-b483-82ab3701d779)
- [tanstack-router-react-example-with-trpc](https://pkg.pr.new/template/264673b6-0aac-475e-92f2-10e8d52f7176)
- [tanstack-router-react-example-with-trpc-react-query](https://pkg.pr.new/template/6fa44779-3c62-4a01-84b2-56bbb4a24013)

</p></details>
      
    


<details><summary><b>@tanstack/create-router</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/create-router@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/history</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/history@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/eslint-plugin-router</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/eslint-plugin-router@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/react-cross-context</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/react-cross-context@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/react-router</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/react-router@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/react-router-with-query</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/react-router-with-query@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/router-arktype-adapter</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-arktype-adapter@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/router-cli</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-cli@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/router-devtools</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-devtools@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/router-generator</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-generator@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/router-plugin</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-plugin@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/router-valibot-adapter</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-valibot-adapter@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/router-vite-plugin</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-vite-plugin@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/start</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/start@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/router-zod-adapter</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/router-zod-adapter@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/start-vite-plugin</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/start-vite-plugin@2669
```

</p></details>
      
    

<details><summary><b>@tanstack/virtual-file-routes</b></summary><p>

```
pnpm add https://pkg.pr.new/@tanstack/virtual-file-routes@2669
```

</p></details>
      
    

_commit: <a href="https://github.com/TanStack/router/runs/32235353521"><code>ed0d856</code></a>_


Patch:
diff --git a/docs/framework/react/guide/data-loading.md b/docs/framework/react/guide/data-loading.md
index b966418a2..21ddc6a84 100644
--- a/docs/framework/react/guide/data-loading.md
+++ b/docs/framework/react/guide/data-loading.md
@@ -78,7 +78,7 @@ The `loader` function receives a single object with the following properties:
 - `deps` - The object value returned from the `Route.loaderDeps` function. If `Route.loaderDeps` is not defined, an empty object will be provided instead.
 - `location` - The current location
 - `params` - The route's path params
-- `parentMatchPromise` - `Promise<RouteMatch>` or `undefined`
+- `parentMatchPromise` - `Promise<RouteMatch>` (`undefined` for the root route)
 - `preload` - Boolean which is `true` when the route is being preloaded instead of loaded
 - `route` - The route itself
 
diff --git a/packages/react-router/src/fileRoute.ts b/packages/react-router/src/fileRoute.ts
index 8962278a0..7b9a25532 100644
--- a/packages/react-router/src/fileRoute.ts
+++ b/packages/react-router/src/fileRoute.ts
@@ -100,6 +100,7 @@ export class FileRoute<
   >(
     options?: FileBaseRouteOptions<
       TParentRoute,
+      TId,
       TPath,
       TSearchValidator,
       TParams,
@@ -161,6 +162,7 @@ export function FileRouteLoader<
     TLoaderFn,
     RouteLoaderFn<
       TRoute['parentRoute'],
+      TRoute['types']['id'],
       TRoute['types']['params'],
       TRoute['types']['loaderDeps'],
       TRoute['types']['routerContext'],
diff --git a/packages/react-router/src/route.ts b/packages/react-router/src/route.ts
index 3a6aef13c..e692939ba 100644
--- a/packages/react-router/src/route.ts
+++ b/packages/react-router/src/route.ts
@@ -57,6 +57,7 @@ export type RoutePathOptionsIntersection<TCustomId, TPath> = {
 
 export type RouteOptions<
   TParentRoute extends AnyRoute = AnyRoute,
+  TId extends string = string,
   TCustomId extends string = string,
   TFullPath extends string = string,
   TPath extends string = string,
@@ -69,6 +70,7 @@ export type RouteOptions<
   TBeforeLoadFn = AnyContext,
 > = BaseRouteOptions<
   TParentRoute,
+  TId,
   TCustomId,
   TPath,
   TSearchValidator,
@@ -173,6 +175,7 @@ export type BeforeLoadFn<
 
 export type FileBaseRouteOptions<
   TParentRoute extends AnyRoute = AnyRoute,
+  TId extends string = string,
   TPath extends string = string,
   TSearchValidator = undefined,
   TParams = {},
@@ -193,6 +196,7 @@ export type FileBaseRouteOptions<
     | ((
         match: LoaderFnContext<
           TParentRoute,
+          TId,
           TParams,
           TLoaderDeps,
           TRouterContext,
@@ -239,6 +243,7 @@ export type FileBaseRouteOptions<
     (
       ctx: LoaderFnContext<
         TParentRoute,
+        TId,
         TParams,
         TLoaderDeps,
         TRouterContext,
@@ -251,6 +256,7 @@ export type FileBaseRouteOptions<
 
 export type BaseRouteOptions<
   TParentRoute extends AnyRoute = AnyRoute,
+  TId extends string = string,
   TCustomId extends string = string,
   TPath extends string = string,
   TSearchValidator = undefined,
@@ -263,6 +269,7 @@ export type BaseRouteOptions<
 > = RoutePathOptions<TCustomId, TPath> &
   FileBaseRouteOptions<
     TParentRoute,
+    TId,
     TPath,
     TSearchValidator,
     TParams,
@@ -555,6 +562,7 @@ export type DefaultSearchValidator = SearchValidator<
 
 export type RouteLoaderFn<
   in out TParentRoute extends AnyRoute = AnyRoute,
+  in out TId extends string = string,
   in out TParams = {},
   in out TLoaderDeps = {},
   in out TRouterContext = {},
@@ -563,6 +571,7 @@ export type RouteLoaderFn<
 > = (
   match: LoaderFnContext<
     TParentRoute,
+    TId,
     TParams,
     TLoaderDeps,
     TRouterContext,
@@ -573,6 +582,7 @@ export type RouteLoaderFn<
 
 export interface LoaderFnContext<
   in out TParentRoute extends AnyRoute = AnyRoute,
+  in out TId extends string = string,
   in out TParams = {},
   in out TLoaderDeps = {},
   in out TRouterContext = {},
@@ -596,7 +606,10 @@ export interface LoaderFnContext<
    * @deprecated Use `throw redirect({ to: '/somewhere' })` instead
    **/
   navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void>
-  parentMatchPromise?: Promise<MakeRouteMatchFromRoute<TParentRoute>>
+  // root route does not have a parent match
+  parentMatchPromise: TId extends RootRouteId
+    ? never
+    : Promise<MakeRouteMatchFromRoute<TParentRoute>>
   cause: 'preload' | 'enter' | 'stay'
   route: Route
 }
@@ -925,6 +938,7 @@ export class Route<
   isRoot: TParentRoute extends Route<any> ? true : false
   options: RouteOptions<
     TParentRoute,
+    TId,
     TCustomId,
     TFullPath,
     TPath,
@@ -997,6 +1011,7 @@ export class Route<
   constructor(
     options?: RouteOptions<
       TParentRoute,
+      TId,
       TCustomId,
       TFullPath,
       TPath,
@@ -1057,6 +1072,7 @@ export class Route<
     const options = this.options as
       | (RouteOptions<
           TParentRoute,
+          TId,
           TCustomId,
           TFullPath,
           TPath,
@@ -1192,6 +1208,7 @@ export class Route<
       TNewLoaderFn,
       RouteLoaderFn<
         TParentRoute,
+        TCustomId,
         TParams,
         TLoaderDeps,
         TRouterContext,
@@ -1340,6 +1357,7 @@ export function createRoute<
 >(
   options: RouteOptions<
     TParentRoute,
+    TId,
     TCustomId,
     TFullPath,
     TPath,
@@ -1381,8 +1399,9 @@ export type RootRouteOptions<
 > = Omit<
   RouteOptions<
     any, // TParentRoute
-    RootRouteId,
-    '', // TCustomId
+    RootRouteId, // TId
+    RootRouteId, // TCustomId
+    '', // TFullPath
     '', // TPath
     TSearchValidator,
     {}, // TParams
@@ -1696,6 +1715,7 @@ export class NotFoundRoute<
         string,
         string,
         string,
+        string,
         TSearchValidator,
         {},
         TLoaderDeps,
diff --git a/packages/react-router/src/router.ts b/packages/react-router/src/router.ts
index da1bf6890..6b2e693e3 100644
--- a/packages/react-router/src/router.ts
+++ b/packages/react-router/src/router.ts
@@ -2310,7 +2310,7 @@ export class Router<
                   if (prevLoaderPromise) {
                     await prevLoaderPromise
                   } else {
-                    const parentMatchPromise = matchPromises[index - 1]
+                    const parentMatchPromise = matchPromises[index - 1] as any
                     const route = this.looseRoutesById[routeId]!
 
                     const getLoaderContext = (): LoaderFnContext => {


Test patch:
diff --git a/packages/react-router/tests/loaders.test.tsx b/packages/react-router/tests/loaders.test.tsx
index 896ffa106..85a9dc15f 100644
--- a/packages/react-router/tests/loaders.test.tsx
+++ b/packages/react-router/tests/loaders.test.tsx
@@ -144,7 +144,7 @@ describe('loaders parentMatchPromise', () => {
       loader: async ({ parentMatchPromise }) => {
         nestedLoaderMock(parentMatchPromise)
         const parentMatch = await parentMatchPromise
-        expect(parentMatch?.loaderData).toBe('nested')
+        expect(parentMatch.loaderData).toBe('nested')
       },
       component: () => <div>Nested Foo page</div>,
     })
diff --git a/packages/react-router/tests/route.test-d.tsx b/packages/react-router/tests/route.test-d.tsx
index 65bdb69b0..372e0e072 100644
--- a/packages/react-router/tests/route.test-d.tsx
+++ b/packages/react-router/tests/route.test-d.tsx
@@ -17,7 +17,6 @@ import type {
   SearchSchemaInput,
 } from '../src'
 import type {
-  MakeRouteMatch,
   MakeRouteMatchFromRoute,
   MakeRouteMatchUnion,
 } from '../src/Matches'
@@ -87,7 +86,7 @@ test('when creating the root with a loader', () => {
         context: {}
         location: ParsedLocation
         navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void>
-        parentMatchPromise?: Promise<MakeRouteMatch>
+        parentMatchPromise: never
         cause: 'preload' | 'enter' | 'stay'
         route: Route
       }>()
@@ -182,7 +181,7 @@ test('when creating the root route with context and a loader', () => {
         context: { userId: string }
         location: ParsedLocation
         navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void>
-        parentMatchPromise?: Promise<MakeRouteMatch>
+        parentMatchPromise: never
         cause: 'preload' | 'enter' | 'stay'
         route: Route
       }>()
@@ -250,7 +249,7 @@ test('when creating the root route with context, routeContext, beforeLoad and a
         context: { userId: string; permission: 'view'; env: 'env1' }
         location: ParsedLocation
         navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void>
-        parentMatchPromise?: Promise<MakeRouteMatch>
+        parentMatchPromise: never
         cause: 'preload' | 'enter' | 'stay'
         route: Route
       }>()
@@ -378,7 +377,7 @@ test('when creating a child route with a loader from the root route', () => {
         context: {}
         location: ParsedLocation
         navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void>
-        parentMatchPromise?: Promise<MakeRouteMatchFromRoute<typeof rootRoute>>
+        parentMatchPromise: Promise<MakeRouteMatchFromRoute<typeof rootRoute>>
         cause: 'preload' | 'enter' | 'stay'
         route: Route
       }>()
@@ -420,7 +419,7 @@ test('when creating a child route with a loader from the root route with context
         context: { userId: string }
         location: ParsedLocation
         navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void>
-        parentMatchPromise?: Promise<MakeRouteMatchFromRoute<typeof rootRoute>>
+        parentMatchPromise: Promise<MakeRouteMatchFromRoute<typeof rootRoute>>
         cause: 'preload' | 'enter' | 'stay'
         route: Route
       }>()
@@ -553,7 +552,7 @@ test('when creating a child route with params, search and loader from the root r
         context: {}
         location: ParsedLocation
         navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void>
-        parentMatchPromise?: Promise<MakeRouteMatchFromRoute<typeof rootRoute>>
+        parentMatchPromise: Promise<MakeRouteMatchFromRoute<typeof rootRoute>>
         cause: 'preload' | 'enter' | 'stay'
         route: Route
       }>
@@ -578,7 +577,7 @@ test('when creating a child route with params, search, loader and loaderDeps fro
         context: {}
         location: ParsedLocation
         navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void>
-        parentMatchPromise?: Promise<MakeRouteMatchFromRoute<typeof rootRoute>>
+        parentMatchPromise: Promise<MakeRouteMatchFromRoute<typeof rootRoute>>
         cause: 'preload' | 'enter' | 'stay'
         route: Route
       }>(),
@@ -602,7 +601,7 @@ test('when creating a child route with params, search, loader and loaderDeps fro
         context: { userId: string }
         location: ParsedLocation
         navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void>
-        parentMatchPromise?: Promise<MakeRouteMatchFromRoute<typeof rootRoute>>
+        parentMatchPromise: Promise<MakeRouteMatchFromRoute<typeof rootRoute>>
         cause: 'preload' | 'enter' | 'stay'
         route: Route
       }>(),
@@ -705,7 +704,7 @@ test('when creating a child route with params, search with routeContext, beforeL
         context: { userId: string; env: string; readonly permission: 'view' }
         location: ParsedLocation
         navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void>
-        parentMatchPromise?: Promise<MakeRouteMatchFromRoute<typeof rootRoute>>
+        parentMatchPromise: Promise<MakeRouteMatchFromRoute<typeof rootRoute>>
         cause: 'preload' | 'enter' | 'stay'
         route: Route
       }>()
@@ -1034,7 +1033,7 @@ test('when creating a child route with routeContext, beforeLoad, search, params,
         }
         location: ParsedLocation
         navigate: (opts: NavigateOptions<AnyRouter>) => Promise<void>
-        parentMatchPromise?: Promise<
+        parentMatchPromise: Promise<
           MakeRouteMatchFromRoute<typeof detailsRoute>
         >
         cause: 'preload' | 'enter' | 'stay'