@tanstack/react-router 0.0.1-beta.214 → 0.0.1-beta.215

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tanstack/react-router",
3
3
  "author": "Tanner Linsley",
4
- "version": "0.0.1-beta.214",
4
+ "version": "0.0.1-beta.215",
5
5
  "license": "MIT",
6
6
  "repository": "tanstack/router",
7
7
  "homepage": "https://tanstack.com/router",
@@ -42,7 +42,7 @@
42
42
  "@babel/runtime": "^7.16.7",
43
43
  "tiny-invariant": "^1.3.1",
44
44
  "tiny-warning": "^1.0.3",
45
- "@tanstack/history": "0.0.1-beta.214"
45
+ "@tanstack/history": "0.0.1-beta.215"
46
46
  },
47
47
  "scripts": {
48
48
  "build": "rollup --config rollup.config.js"
package/src/Matches.tsx CHANGED
@@ -8,7 +8,7 @@ import { ResolveRelativePath, ToOptions } from './link'
8
8
  import { AnyRoute, ReactNode, rootRouteId } from './route'
9
9
  import { RouteById, RouteByPath, RouteIds, RoutePaths } from './routeInfo'
10
10
  import { RegisteredRouter } from './router'
11
- import { NoInfer, StrictOrFrom } from './utils'
11
+ import { NoInfer, StrictOrFrom, functionalUpdate } from './utils'
12
12
 
13
13
  export function Matches() {
14
14
  const { routesById, state } = useRouter()
@@ -16,7 +16,7 @@ export function Matches() {
16
16
 
17
17
  const locationKey = useRouterState().location.state.key
18
18
 
19
- const route = routesById[rootRouteId]
19
+ const route = routesById[rootRouteId]!
20
20
 
21
21
  const errorComponent = React.useCallback(
22
22
  (props: any) => {
@@ -58,7 +58,7 @@ export function Match({ matches }: { matches: RouteMatch[] }) {
58
58
  const { options, routesById } = useRouter()
59
59
  const match = matches[0]!
60
60
  const routeId = match?.routeId
61
- const route = routesById[routeId]
61
+ const route = routesById[routeId]!
62
62
  const locationKey = useRouterState().location.state?.key
63
63
 
64
64
  const PendingComponent = (route.options.pendingComponent ??
@@ -70,9 +70,9 @@ export function Match({ matches }: { matches: RouteMatch[] }) {
70
70
  options.defaultErrorComponent ??
71
71
  ErrorComponent
72
72
 
73
- const ResolvedSuspenseBoundary =
74
- route.options.wrapInSuspense ?? React.Suspense
75
- // const ResolvedSuspenseBoundary = SafeFragment
73
+ const ResolvedSuspenseBoundary = route.options.wrapInSuspense
74
+ ? React.Suspense
75
+ : SafeFragment
76
76
 
77
77
  const errorComponent = React.useCallback(
78
78
  (props: any) => {
@@ -112,7 +112,7 @@ export function Match({ matches }: { matches: RouteMatch[] }) {
112
112
  }
113
113
  function MatchInner({ match }: { match: RouteMatch }): any {
114
114
  const { options, routesById } = useRouter()
115
- const route = routesById[match.routeId]
115
+ const route = routesById[match.routeId]!
116
116
 
117
117
  if (match.status === 'error') {
118
118
  throw match.error
@@ -131,7 +131,8 @@ function MatchInner({ match }: { match: RouteMatch }): any {
131
131
  useRouteContext: route.useRouteContext as any,
132
132
  useSearch: route.useSearch,
133
133
  useParams: route.useParams as any,
134
- } as any)
134
+ useLoaderData: route.useLoaderData,
135
+ })
135
136
  }
136
137
 
137
138
  return <Outlet />
@@ -248,7 +249,7 @@ export function useMatch<
248
249
  opts: StrictOrFrom<TFrom> & {
249
250
  select?: (match: TRouteMatchState) => TSelected
250
251
  },
251
- ): TStrict extends true ? TRouteMatchState : TRouteMatchState | undefined {
252
+ ): TStrict extends true ? TSelected : TSelected | undefined {
252
253
  const nearestMatch = React.useContext(matchesContext)[0]!
253
254
  const nearestMatchRouteId = nearestMatch?.routeId
254
255
 
@@ -313,3 +314,24 @@ export function useMatches<T = RouteMatch[]>(opts?: {
313
314
  },
314
315
  })
315
316
  }
317
+
318
+ export function useLoaderData<
319
+ TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
320
+ TFrom extends RouteIds<TRouteTree> = RouteIds<TRouteTree>,
321
+ TStrict extends boolean = true,
322
+ TRouteMatch extends RouteMatch<TRouteTree, TFrom> = RouteMatch<
323
+ TRouteTree,
324
+ TFrom
325
+ >,
326
+ TSelected = TRouteMatch['loaderData'],
327
+ >(
328
+ opts: StrictOrFrom<TFrom> & {
329
+ select?: (match: TRouteMatch) => TSelected
330
+ },
331
+ ): TStrict extends true ? TSelected : TSelected | undefined {
332
+ const match = useMatch({ ...opts, select: undefined })!
333
+
334
+ return typeof opts.select === 'function'
335
+ ? opts.select(match?.loaderData)
336
+ : match?.loaderData
337
+ }
@@ -83,16 +83,13 @@ export type BuildLinkFn<TRouteTree extends AnyRoute> = <
83
83
  ) => LinkInfo
84
84
 
85
85
  export type NavigateFn<TRouteTree extends AnyRoute> = <
86
- TRouteTree extends AnyRoute,
87
86
  TFrom extends RoutePaths<TRouteTree> = '/',
88
87
  TTo extends string = '',
89
88
  TMaskFrom extends RoutePaths<TRouteTree> = TFrom,
90
89
  TMaskTo extends string = '',
91
- >({
92
- from,
93
- to = '' as any,
94
- ...rest
95
- }: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>) => Promise<void>
90
+ >(
91
+ opts: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>,
92
+ ) => Promise<void>
96
93
 
97
94
  export type MatchRouteFn<TRouteTree extends AnyRoute> = <
98
95
  TFrom extends RoutePaths<TRouteTree> = '/',
@@ -250,12 +247,14 @@ export function RouterProvider<
250
247
  getInitialRouterState(latestLocationRef.current),
251
248
  )
252
249
  const [isTransitioning, startReactTransition] = React.useTransition()
250
+ const pendingMatchesRef = React.useRef<AnyRouteMatch[]>([])
253
251
 
254
252
  const state = React.useMemo<RouterState<TRouteTree>>(
255
253
  () => ({
256
254
  ...preState,
257
255
  status: isTransitioning ? 'pending' : 'idle',
258
256
  location: isTransitioning ? latestLocationRef.current : preState.location,
257
+ pendingMatches: pendingMatchesRef.current,
259
258
  }),
260
259
  [preState, isTransitioning],
261
260
  )
@@ -268,6 +267,8 @@ export function RouterProvider<
268
267
  toLocation: state.location,
269
268
  pathChanged: state.location!.href !== state.resolvedLocation?.href,
270
269
  })
270
+ pendingMatchesRef.current = []
271
+
271
272
  setState((s) => ({
272
273
  ...s,
273
274
  resolvedLocation: s.location,
@@ -464,7 +465,7 @@ export function RouterProvider<
464
465
 
465
466
  // Create a fresh route match
466
467
  const hasLoaders = !!(
467
- route.options.load ||
468
+ route.options.loader ||
468
469
  componentTypes.some((d) => (route.options[d] as any)?.preload)
469
470
  )
470
471
 
@@ -938,10 +939,6 @@ export function RouterProvider<
938
939
  const parentMatchPromise = matchPromises[index - 1]
939
940
  const route = looseRoutesById[match.routeId]!
940
941
 
941
- if (match.isFetching) {
942
- return getRouteMatch(state, match.id)?.loadPromise
943
- }
944
-
945
942
  const handleIfRedirect = (err: any) => {
946
943
  if (isRedirect(err)) {
947
944
  if (!preload) {
@@ -952,90 +949,101 @@ export function RouterProvider<
952
949
  return false
953
950
  }
954
951
 
955
- const load = async () => {
956
- try {
957
- const componentsPromise = Promise.all(
958
- componentTypes.map(async (type) => {
959
- const component = route.options[type]
960
-
961
- if ((component as any)?.preload) {
962
- await (component as any).preload()
963
- }
964
- }),
965
- )
966
-
967
- const loaderPromise = route.options.load?.({
968
- params: match.params,
969
- search: match.search,
970
- preload: !!preload,
971
- parentMatchPromise,
972
- abortController: match.abortController,
973
- context: match.context,
974
- location: state.location,
975
- navigate: (opts) =>
976
- navigate({ ...opts, from: match.pathname }),
977
- })
978
-
979
- const [_, loaderContext] = await Promise.all([
980
- componentsPromise,
981
- loaderPromise,
982
- ])
983
- if ((latestPromise = checkLatest())) return await latestPromise
984
-
985
- matches[index] = match = {
986
- ...match,
987
- error: undefined,
988
- status: 'success',
989
- isFetching: false,
990
- updatedAt: Date.now(),
991
- }
992
- } catch (error) {
993
- if ((latestPromise = checkLatest())) return await latestPromise
994
- if (handleIfRedirect(error)) return
995
-
996
- try {
997
- route.options.onError?.(error)
998
- } catch (onErrorError) {
999
- error = onErrorError
1000
- if (handleIfRedirect(onErrorError)) return
1001
- }
1002
-
1003
- matches[index] = match = {
1004
- ...match,
1005
- error,
1006
- status: 'error',
1007
- isFetching: false,
1008
- updatedAt: Date.now(),
1009
- }
1010
- }
1011
-
1012
- if (!preload) {
1013
- setState((s) => ({
1014
- ...s,
1015
- matches: s.matches.map((d) =>
1016
- d.id === match.id ? match : d,
1017
- ),
1018
- }))
1019
- }
1020
- }
1021
-
1022
952
  let loadPromise: Promise<void> | undefined
1023
953
 
1024
954
  matches[index] = match = {
1025
955
  ...match,
1026
- isFetching: true,
1027
956
  fetchedAt: Date.now(),
1028
957
  invalid: false,
1029
958
  }
1030
959
 
1031
- loadPromise = load()
960
+ if (match.isFetching) {
961
+ loadPromise = getRouteMatch(state, match.id)?.loadPromise
962
+ } else {
963
+ matches[index] = match = {
964
+ ...match,
965
+ isFetching: true,
966
+ }
967
+
968
+ const componentsPromise = Promise.all(
969
+ componentTypes.map(async (type) => {
970
+ const component = route.options[type]
971
+
972
+ if ((component as any)?.preload) {
973
+ await (component as any).preload()
974
+ }
975
+ }),
976
+ )
977
+
978
+ const loaderPromise = route.options.loader?.({
979
+ params: match.params,
980
+ search: match.search,
981
+ preload: !!preload,
982
+ parentMatchPromise,
983
+ abortController: match.abortController,
984
+ context: match.context,
985
+ location: state.location,
986
+ navigate: (opts) =>
987
+ navigate({ ...opts, from: match.pathname } as any),
988
+ })
989
+
990
+ loadPromise = Promise.all([
991
+ componentsPromise,
992
+ loaderPromise,
993
+ ]).then((d) => d[1])
994
+ }
1032
995
 
1033
996
  matches[index] = match = {
1034
997
  ...match,
1035
998
  loadPromise,
1036
999
  }
1037
1000
 
1038
- await loadPromise
1001
+ if (!preload) {
1002
+ setState((s) => ({
1003
+ ...s,
1004
+ matches: s.matches.map((d) => (d.id === match.id ? match : d)),
1005
+ }))
1006
+ }
1007
+
1008
+ try {
1009
+ const loaderData = await loadPromise
1010
+ if ((latestPromise = checkLatest())) return await latestPromise
1011
+
1012
+ matches[index] = match = {
1013
+ ...match,
1014
+ error: undefined,
1015
+ status: 'success',
1016
+ isFetching: false,
1017
+ updatedAt: Date.now(),
1018
+ loaderData,
1019
+ loadPromise: undefined,
1020
+ }
1021
+ } catch (error) {
1022
+ if ((latestPromise = checkLatest())) return await latestPromise
1023
+ if (handleIfRedirect(error)) return
1024
+
1025
+ try {
1026
+ route.options.onError?.(error)
1027
+ } catch (onErrorError) {
1028
+ error = onErrorError
1029
+ if (handleIfRedirect(onErrorError)) return
1030
+ }
1031
+
1032
+ matches[index] = match = {
1033
+ ...match,
1034
+ error,
1035
+ status: 'error',
1036
+ isFetching: false,
1037
+ updatedAt: Date.now(),
1038
+ }
1039
+ }
1040
+
1041
+ if (!preload) {
1042
+ setState((s) => ({
1043
+ ...s,
1044
+ matches: s.matches.map((d) => (d.id === match.id ? match : d)),
1045
+ }))
1046
+ }
1039
1047
  })(),
1040
1048
  )
1041
1049
  })
@@ -1071,6 +1079,8 @@ export function RouterProvider<
1071
1079
  },
1072
1080
  )
1073
1081
 
1082
+ pendingMatchesRef.current = matches
1083
+
1074
1084
  const previousMatches = state.matches
1075
1085
 
1076
1086
  // Ingest the new matches
@@ -1099,13 +1109,13 @@ export function RouterProvider<
1099
1109
  }
1100
1110
 
1101
1111
  const exitingMatchIds = previousMatches.filter(
1102
- (id) => !state.pendingMatches.includes(id),
1112
+ (id) => !pendingMatchesRef.current.includes(id),
1103
1113
  )
1104
- const enteringMatchIds = state.pendingMatches.filter(
1114
+ const enteringMatchIds = pendingMatchesRef.current.filter(
1105
1115
  (id) => !previousMatches.includes(id),
1106
1116
  )
1107
1117
  const stayingMatchIds = previousMatches.filter((id) =>
1108
- state.pendingMatches.includes(id),
1118
+ pendingMatchesRef.current.includes(id),
1109
1119
  )
1110
1120
 
1111
1121
  // setState((s) => ({
@@ -1326,10 +1336,7 @@ export function RouterProvider<
1326
1336
  }
1327
1337
  }, [history])
1328
1338
 
1329
- const initialLoad = React.useRef(true)
1330
-
1331
- if (initialLoad.current) {
1332
- initialLoad.current = false
1339
+ React.useLayoutEffect(() => {
1333
1340
  startReactTransition(() => {
1334
1341
  try {
1335
1342
  load()
@@ -1337,7 +1344,7 @@ export function RouterProvider<
1337
1344
  console.error(err)
1338
1345
  }
1339
1346
  })
1340
- }
1347
+ }, [])
1341
1348
 
1342
1349
  const matchRoute = useStableCallback<MatchRouteFn<TRouteTree>>(
1343
1350
  (location, opts) => {
@@ -1454,6 +1461,7 @@ export interface RouteMatch<
1454
1461
  searchError: unknown
1455
1462
  updatedAt: number
1456
1463
  loadPromise?: Promise<void>
1464
+ loaderData?: RouteById<TRouteTree, TRouteId>['types']['loaderData']
1457
1465
  __resolveLoadPromise?: () => void
1458
1466
  context: RouteById<TRouteTree, TRouteId>['types']['allContext']
1459
1467
  routeSearch: RouteById<TRouteTree, TRouteId>['types']['searchSchema']
package/src/fileRoute.ts CHANGED
@@ -104,6 +104,7 @@ export class FileRoute<
104
104
  Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>
105
105
  >,
106
106
  TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
107
+ TLoaderData extends any = unknown,
107
108
  TChildren extends RouteConstraints['TChildren'] = unknown,
108
109
  TRouteTree extends RouteConstraints['TRouteTree'] = AnyRoute,
109
110
  >(
@@ -117,11 +118,17 @@ export class FileRoute<
117
118
  TParams,
118
119
  TAllParams,
119
120
  TRouteContext,
120
- TContext
121
+ TContext,
122
+ TLoaderData
121
123
  >,
122
124
  'getParentRoute' | 'path' | 'id'
123
125
  > &
124
- UpdatableRouteOptions<TFullSearchSchema, TAllParams, TContext>,
126
+ UpdatableRouteOptions<
127
+ TFullSearchSchema,
128
+ TAllParams,
129
+ TContext,
130
+ TLoaderData
131
+ >,
125
132
  ): Route<
126
133
  TParentRoute,
127
134
  TPath,
@@ -135,6 +142,7 @@ export class FileRoute<
135
142
  TRouteContext,
136
143
  TContext,
137
144
  TRouterContext,
145
+ TLoaderData,
138
146
  TChildren,
139
147
  TRouteTree
140
148
  > => {
package/src/route.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { HistoryLocation } from '@tanstack/history'
2
2
  import * as React from 'react'
3
3
  import invariant from 'tiny-invariant'
4
- import { useMatch } from './Matches'
4
+ import { useLoaderData, useMatch } from './Matches'
5
5
  import { AnyRouteMatch } from './RouterProvider'
6
6
  import { NavigateOptions, ParsePathParams, ToSubOptions } from './link'
7
7
  import { ParsedLocation } from './location'
@@ -63,6 +63,7 @@ export type RouteOptions<
63
63
  TAllParams extends AnyPathParams = TParams,
64
64
  TRouteContext extends RouteContext = RouteContext,
65
65
  TAllContext extends Record<string, any> = AnyContext,
66
+ TLoaderData extends any = unknown,
66
67
  > = BaseRouteOptions<
67
68
  TParentRoute,
68
69
  TCustomId,
@@ -72,9 +73,17 @@ export type RouteOptions<
72
73
  TParams,
73
74
  TAllParams,
74
75
  TRouteContext,
75
- TAllContext
76
+ TAllContext,
77
+ TLoaderData
76
78
  > &
77
- NoInfer<UpdatableRouteOptions<TFullSearchSchema, TAllParams, TAllContext>>
79
+ NoInfer<
80
+ UpdatableRouteOptions<
81
+ TFullSearchSchema,
82
+ TAllParams,
83
+ TAllContext,
84
+ TLoaderData
85
+ >
86
+ >
78
87
 
79
88
  export type ParamsFallback<
80
89
  TPath extends string,
@@ -95,6 +104,7 @@ export type BaseRouteOptions<
95
104
  TAllParams = ParamsFallback<TPath, TParams>,
96
105
  TRouteContext extends RouteContext = RouteContext,
97
106
  TAllContext extends Record<string, any> = AnyContext,
107
+ TLoaderData extends any = unknown,
98
108
  > = RoutePathOptions<TCustomId, TPath> & {
99
109
  getParentRoute: () => TParentRoute
100
110
  validateSearch?: SearchSchemaValidator<TSearchSchema>
@@ -119,11 +129,12 @@ export type BaseRouteOptions<
119
129
  TRouteContext
120
130
  >
121
131
  }) & {
122
- load?: RouteLoadFn<
132
+ loader?: RouteLoadFn<
123
133
  TAllParams,
124
134
  TFullSearchSchema,
125
135
  NoInfer<TAllContext>,
126
- NoInfer<TRouteContext>
136
+ NoInfer<TRouteContext>,
137
+ TLoaderData
127
138
  >
128
139
  } & (
129
140
  | {
@@ -163,6 +174,7 @@ export type UpdatableRouteOptions<
163
174
  TFullSearchSchema extends Record<string, any>,
164
175
  TAllParams extends AnyPathParams,
165
176
  TAllContext extends AnyContext,
177
+ TLoaderData extends any = unknown,
166
178
  > = MetaOptions & {
167
179
  // test?: (args: TAllContext) => void
168
180
  // If true, this route will be matched as case-sensitive
@@ -170,7 +182,12 @@ export type UpdatableRouteOptions<
170
182
  // If true, this route will be forcefully wrapped in a suspense boundary
171
183
  wrapInSuspense?: boolean
172
184
  // The content to be rendered when the route is matched. If no component is provided, defaults to `<Outlet />`
173
- component?: RouteComponent<TFullSearchSchema, TAllParams, TAllContext>
185
+ component?: RouteComponent<
186
+ TFullSearchSchema,
187
+ TAllParams,
188
+ TAllContext,
189
+ TLoaderData
190
+ >
174
191
  // The content to be rendered when the route encounters an error
175
192
  errorComponent?: ErrorRouteComponent<
176
193
  TFullSearchSchema,
@@ -244,8 +261,9 @@ export type RouteLoadFn<
244
261
  TFullSearchSchema extends Record<string, any> = {},
245
262
  TAllContext extends Record<string, any> = AnyContext,
246
263
  TRouteContext extends Record<string, any> = AnyContext,
264
+ TLoaderData extends any = unknown,
247
265
  > = (
248
- match: LoadFnContext<
266
+ match: LoaderFnContext<
249
267
  TAllParams,
250
268
  TFullSearchSchema,
251
269
  TAllContext,
@@ -253,9 +271,9 @@ export type RouteLoadFn<
253
271
  > & {
254
272
  parentMatchPromise?: Promise<void>
255
273
  },
256
- ) => any
274
+ ) => Promise<TLoaderData> | TLoaderData
257
275
 
258
- export interface LoadFnContext<
276
+ export interface LoaderFnContext<
259
277
  TAllParams = {},
260
278
  TFullSearchSchema extends Record<string, any> = {},
261
279
  TAllContext extends Record<string, any> = AnyContext,
@@ -378,6 +396,7 @@ export class Route<
378
396
  Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>
379
397
  >,
380
398
  TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
399
+ TLoaderData extends any = unknown,
381
400
  TChildren extends RouteConstraints['TChildren'] = unknown,
382
401
  TRouteTree extends RouteConstraints['TRouteTree'] = AnyRoute,
383
402
  > {
@@ -391,7 +410,8 @@ export class Route<
391
410
  TParams,
392
411
  TAllParams,
393
412
  TRouteContext,
394
- TAllContext
413
+ TAllContext,
414
+ TLoaderData
395
415
  >
396
416
 
397
417
  test!: Expand<
@@ -422,7 +442,8 @@ export class Route<
422
442
  TParams,
423
443
  TAllParams,
424
444
  TRouteContext,
425
- TAllContext
445
+ TAllContext,
446
+ TLoaderData
426
447
  >,
427
448
  ) {
428
449
  this.options = (options as any) || {}
@@ -446,6 +467,7 @@ export class Route<
446
467
  children: TChildren
447
468
  routeTree: TRouteTree
448
469
  routerContext: TRouterContext
470
+ loaderData: TLoaderData
449
471
  }
450
472
 
451
473
  init = (opts: { originalIndex: number }) => {
@@ -460,7 +482,8 @@ export class Route<
460
482
  TParams,
461
483
  TAllParams,
462
484
  TRouteContext,
463
- TAllContext
485
+ TAllContext,
486
+ TLoaderData
464
487
  > &
465
488
  RoutePathOptionsIntersection<TCustomId, TPath>
466
489
 
@@ -542,7 +565,8 @@ export class Route<
542
565
  TAllParams,
543
566
  Expand<
544
567
  Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>
545
- >
568
+ >,
569
+ TLoaderData
546
570
  >,
547
571
  ) => {
548
572
  Object.assign(this.options, options)
@@ -578,6 +602,11 @@ export class Route<
578
602
  }): TSelected => {
579
603
  return useParams({ ...opts, from: this.id } as any)
580
604
  }
605
+ useLoaderData = <TSelected = TLoaderData>(opts?: {
606
+ select?: (search: TLoaderData) => TSelected
607
+ }): TSelected => {
608
+ return useLoaderData({ ...opts, from: this.id } as any) as any
609
+ }
581
610
  }
582
611
 
583
612
  export type AnyRootRoute = RootRoute<any, any, any>
@@ -586,6 +615,7 @@ export function rootRouteWithContext<TRouterContext extends {}>() {
586
615
  return <
587
616
  TSearchSchema extends Record<string, any> = {},
588
617
  TRouteContext extends RouteContext = RouteContext,
618
+ TLoaderData extends any = unknown,
589
619
  >(
590
620
  options?: Omit<
591
621
  RouteOptions<
@@ -597,7 +627,8 @@ export function rootRouteWithContext<TRouterContext extends {}>() {
597
627
  {}, // TParams
598
628
  {}, // TAllParams
599
629
  TRouteContext, // TRouteContext
600
- Assign<TRouterContext, TRouteContext> // TAllContext
630
+ Assign<TRouterContext, TRouteContext>, // TAllContext
631
+ TLoaderData // TLoaderData
601
632
  >,
602
633
  | 'path'
603
634
  | 'id'
@@ -615,6 +646,7 @@ export class RootRoute<
615
646
  TSearchSchema extends Record<string, any> = {},
616
647
  TRouteContext extends RouteContext = RouteContext,
617
648
  TRouterContext extends {} = {},
649
+ TLoaderData extends any = unknown,
618
650
  > extends Route<
619
651
  any, // TParentRoute
620
652
  '/', // TPath
@@ -628,6 +660,7 @@ export class RootRoute<
628
660
  TRouteContext, // TRouteContext
629
661
  Expand<Assign<TRouterContext, TRouteContext>>, // TAllContext
630
662
  TRouterContext, // TRouterContext
663
+ TLoaderData,
631
664
  any, // TChildren
632
665
  any // TRouteTree
633
666
  > {
@@ -642,7 +675,8 @@ export class RootRoute<
642
675
  {}, // TParams
643
676
  {}, // TAllParams
644
677
  TRouteContext, // TRouteContext
645
- Assign<TRouterContext, TRouteContext> // TAllContext
678
+ Assign<TRouterContext, TRouteContext>, // TAllContext
679
+ TLoaderData
646
680
  >,
647
681
  | 'path'
648
682
  | 'id'
@@ -720,6 +754,7 @@ export type RouteProps<
720
754
  TFullSearchSchema extends Record<string, any> = AnySearchSchema,
721
755
  TAllParams extends AnyPathParams = AnyPathParams,
722
756
  TAllContext extends Record<string, any> = AnyContext,
757
+ TLoaderData extends any = unknown,
723
758
  > = {
724
759
  useMatch: <TSelected = TAllContext>(opts?: {
725
760
  select?: (search: TAllContext) => TSelected
@@ -733,6 +768,9 @@ export type RouteProps<
733
768
  useParams: <TSelected = TAllParams>(opts?: {
734
769
  select?: (search: TAllParams) => TSelected
735
770
  }) => TSelected
771
+ useLoaderData: <TSelected = TLoaderData>(opts?: {
772
+ select?: (search: TLoaderData) => TSelected
773
+ }) => TSelected
736
774
  }
737
775
 
738
776
  export type ErrorRouteProps<
@@ -765,7 +803,10 @@ export type RouteComponent<
765
803
  TFullSearchSchema extends Record<string, any>,
766
804
  TAllParams extends AnyPathParams,
767
805
  TAllContext extends Record<string, any>,
768
- > = AsyncRouteComponent<RouteProps<TFullSearchSchema, TAllParams, TAllContext>>
806
+ TLoaderData extends any = unknown,
807
+ > = AsyncRouteComponent<
808
+ RouteProps<TFullSearchSchema, TAllParams, TAllContext, TLoaderData>
809
+ >
769
810
 
770
811
  export type ErrorRouteComponent<
771
812
  TFullSearchSchema extends Record<string, any>,
@@ -783,4 +824,4 @@ export type PendingRouteComponent<
783
824
  PendingRouteProps<TFullSearchSchema, TAllParams, TAllContext>
784
825
  >
785
826
 
786
- export type AnyRouteComponent = RouteComponent<any, any, any>
827
+ export type AnyRouteComponent = RouteComponent<any, any, any, any>
package/src/router.ts CHANGED
@@ -36,7 +36,7 @@ export interface Register {
36
36
  // router: Router
37
37
  }
38
38
 
39
- export type AnyRouter = Router<any, any>
39
+ export type AnyRouter = Router<AnyRoute, any>
40
40
 
41
41
  export type RegisteredRouter = Register extends {
42
42
  router: infer TRouter extends AnyRouter