@tanstack/react-router 1.9.0 → 1.11.0

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/src/route.ts CHANGED
@@ -9,15 +9,9 @@ import { RouteById, RouteIds, RoutePaths } from './routeInfo'
9
9
  import { AnyRouter, RegisteredRouter } from './router'
10
10
  import { useParams } from './useParams'
11
11
  import { useSearch } from './useSearch'
12
- import {
13
- Assign,
14
- Expand,
15
- IsAny,
16
- NoInfer,
17
- PickRequired,
18
- UnionToIntersection,
19
- } from './utils'
12
+ import { Assign, Expand, IsAny, NoInfer, UnionToIntersection } from './utils'
20
13
  import { BuildLocationFn, NavigateFn } from './RouterProvider'
14
+ import { LazyRoute } from '.'
21
15
 
22
16
  export const rootRouteId = '__root__' as const
23
17
  export type RootRouteId = typeof rootRouteId
@@ -81,7 +75,7 @@ export type RouteOptions<
81
75
  TLoaderDeps,
82
76
  TLoaderData
83
77
  > &
84
- UpdatableRouteOptions<NoInfer<TFullSearchSchema>>
78
+ UpdatableRouteOptions<NoInfer<TFullSearchSchema>, NoInfer<TLoaderData>>
85
79
 
86
80
  export type ParamsFallback<
87
81
  TPath extends string,
@@ -178,6 +172,7 @@ type BeforeLoadFn<
178
172
 
179
173
  export type UpdatableRouteOptions<
180
174
  TFullSearchSchema extends Record<string, any>,
175
+ TLoaderData extends any,
181
176
  > = {
182
177
  // test?: (args: TAllContext) => void
183
178
  // If true, this route will be matched as case-sensitive
@@ -206,8 +201,30 @@ export type UpdatableRouteOptions<
206
201
  onEnter?: (match: AnyRouteMatch) => void
207
202
  onStay?: (match: AnyRouteMatch) => void
208
203
  onLeave?: (match: AnyRouteMatch) => void
204
+ meta?: (ctx: { loaderData: TLoaderData }) => JSX.IntrinsicElements['meta'][]
205
+ links?: () => JSX.IntrinsicElements['link'][]
206
+ scripts?: () => JSX.IntrinsicElements['script'][]
209
207
  }
210
208
 
209
+ export type MetaDescriptor =
210
+ | { charSet: 'utf-8' }
211
+ | { title: string }
212
+ | { name: string; content: string }
213
+ | { property: string; content: string }
214
+ | { httpEquiv: string; content: string }
215
+ | { 'script:ld+json': LdJsonObject }
216
+ | { tagName: 'meta' | 'link'; [name: string]: string }
217
+ | { [name: string]: unknown }
218
+
219
+ type LdJsonObject = { [Key in string]: LdJsonValue } & {
220
+ [Key in string]?: LdJsonValue | undefined
221
+ }
222
+ type LdJsonArray = LdJsonValue[] | readonly LdJsonValue[]
223
+ type LdJsonPrimitive = string | number | boolean | null
224
+ type LdJsonValue = LdJsonPrimitive | LdJsonObject | LdJsonArray
225
+
226
+ export type RouteLinkEntry = {}
227
+
211
228
  export type ParseParamsOption<TPath extends string, TParams> = ParseParamsFn<
212
229
  TPath,
213
230
  TParams
@@ -333,11 +350,12 @@ export type MergeFromFromParent<T, U> = IsAny<T, U, T & U>
333
350
  export type ResolveAllParams<
334
351
  TParentRoute extends AnyRoute,
335
352
  TParams extends AnyPathParams,
336
- > = Record<never, string> extends TParentRoute['types']['allParams']
337
- ? TParams
338
- : Expand<
339
- UnionToIntersection<TParentRoute['types']['allParams'] & TParams> & {}
340
- >
353
+ > =
354
+ Record<never, string> extends TParentRoute['types']['allParams']
355
+ ? TParams
356
+ : Expand<
357
+ UnionToIntersection<TParentRoute['types']['allParams'] & TParams> & {}
358
+ >
341
359
 
342
360
  export type RouteConstraints = {
343
361
  TParentRoute: AnyRoute
@@ -455,6 +473,32 @@ export type RouteConstraints = {
455
473
  // }
456
474
  // }
457
475
 
476
+ export function getRouteApi<
477
+ TId extends RouteIds<RegisteredRouter['routeTree']>,
478
+ TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
479
+ TFullSearchSchema extends Record<
480
+ string,
481
+ any
482
+ > = TRoute['types']['fullSearchSchema'],
483
+ TAllParams extends AnyPathParams = TRoute['types']['allParams'],
484
+ TAllContext extends Record<string, any> = TRoute['types']['allContext'],
485
+ TLoaderDeps extends Record<string, any> = TRoute['types']['loaderDeps'],
486
+ TLoaderData extends any = TRoute['types']['loaderData'],
487
+ >(id: TId) {
488
+ return new RouteApi<
489
+ TId,
490
+ TRoute,
491
+ TFullSearchSchema,
492
+ TAllParams,
493
+ TAllContext,
494
+ TLoaderDeps,
495
+ TLoaderData
496
+ >({ id })
497
+ }
498
+
499
+ /**
500
+ * @deprecated Use the `getRouteApi` function instead.
501
+ */
458
502
  export class RouteApi<
459
503
  TId extends RouteIds<RegisteredRouter['routeTree']>,
460
504
  TRoute extends AnyRoute = RouteById<RegisteredRouter['routeTree'], TId>,
@@ -513,6 +557,9 @@ export class RouteApi<
513
557
  }
514
558
  }
515
559
 
560
+ /**
561
+ * @deprecated Use the `createRoute` function instead.
562
+ */
516
563
  export class Route<
517
564
  TParentRoute extends RouteConstraints['TParentRoute'] = AnyRoute,
518
565
  TPath extends RouteConstraints['TPath'] = '/',
@@ -587,10 +634,6 @@ export class Route<
587
634
  TLoaderData
588
635
  >
589
636
 
590
- test!: Expand<
591
- Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>
592
- >
593
-
594
637
  // Set up in this.init()
595
638
  parentRoute!: TParentRoute
596
639
  id!: TId
@@ -604,6 +647,7 @@ export class Route<
604
647
  originalIndex?: number
605
648
  router?: AnyRouter
606
649
  rank!: number
650
+ lazyFn?: () => Promise<LazyRoute<any>>
607
651
 
608
652
  constructor(
609
653
  options: RouteOptions<
@@ -792,11 +836,16 @@ export class Route<
792
836
  >
793
837
  }
794
838
 
795
- update = (options: UpdatableRouteOptions<TFullSearchSchema>) => {
839
+ update = (options: UpdatableRouteOptions<TFullSearchSchema, TLoaderData>) => {
796
840
  Object.assign(this.options, options)
797
841
  return this
798
842
  }
799
843
 
844
+ lazy = (lazyFn: () => Promise<LazyRoute<any>>) => {
845
+ this.lazyFn = lazyFn
846
+ return this
847
+ }
848
+
800
849
  useMatch = <TSelected = TAllContext>(opts?: {
801
850
  select?: (search: TAllContext) => TSelected
802
851
  }): TSelected => {
@@ -838,6 +887,103 @@ export class Route<
838
887
  }
839
888
  }
840
889
 
890
+ export function createRoute<
891
+ TParentRoute extends RouteConstraints['TParentRoute'] = AnyRoute,
892
+ TPath extends RouteConstraints['TPath'] = '/',
893
+ TFullPath extends RouteConstraints['TFullPath'] = ResolveFullPath<
894
+ TParentRoute,
895
+ TPath
896
+ >,
897
+ TCustomId extends RouteConstraints['TCustomId'] = string,
898
+ TId extends RouteConstraints['TId'] = ResolveId<
899
+ TParentRoute,
900
+ TCustomId,
901
+ TPath
902
+ >,
903
+ TSearchSchemaInput extends RouteConstraints['TSearchSchema'] = {},
904
+ TSearchSchema extends RouteConstraints['TSearchSchema'] = {},
905
+ TSearchSchemaUsed extends Record<
906
+ string,
907
+ any
908
+ > = TSearchSchemaInput extends SearchSchemaInput
909
+ ? Omit<TSearchSchemaInput, keyof SearchSchemaInput>
910
+ : TSearchSchema,
911
+ TFullSearchSchemaInput extends Record<
912
+ string,
913
+ any
914
+ > = ResolveFullSearchSchemaInput<TParentRoute, TSearchSchemaUsed>,
915
+ TFullSearchSchema extends
916
+ RouteConstraints['TFullSearchSchema'] = ResolveFullSearchSchema<
917
+ TParentRoute,
918
+ TSearchSchema
919
+ >,
920
+ TParams extends RouteConstraints['TParams'] = Expand<
921
+ Record<ParsePathParams<TPath>, string>
922
+ >,
923
+ TAllParams extends RouteConstraints['TAllParams'] = ResolveAllParams<
924
+ TParentRoute,
925
+ TParams
926
+ >,
927
+ TRouteContextReturn extends RouteConstraints['TRouteContext'] = RouteContext,
928
+ TRouteContext extends RouteConstraints['TRouteContext'] = [
929
+ TRouteContextReturn,
930
+ ] extends [never]
931
+ ? RouteContext
932
+ : TRouteContextReturn,
933
+ TAllContext extends Expand<
934
+ Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>
935
+ > = Expand<
936
+ Assign<IsAny<TParentRoute['types']['allContext'], {}>, TRouteContext>
937
+ >,
938
+ TRouterContext extends RouteConstraints['TRouterContext'] = AnyContext,
939
+ TLoaderDeps extends Record<string, any> = {},
940
+ TLoaderData extends any = unknown,
941
+ TChildren extends RouteConstraints['TChildren'] = unknown,
942
+ TRouteTree extends RouteConstraints['TRouteTree'] = AnyRoute,
943
+ >(
944
+ options: RouteOptions<
945
+ TParentRoute,
946
+ TCustomId,
947
+ TPath,
948
+ TSearchSchemaInput,
949
+ TSearchSchema,
950
+ TSearchSchemaUsed,
951
+ TFullSearchSchemaInput,
952
+ TFullSearchSchema,
953
+ TParams,
954
+ TAllParams,
955
+ TRouteContextReturn,
956
+ TRouteContext,
957
+ TRouterContext,
958
+ TAllContext,
959
+ TLoaderDeps,
960
+ TLoaderData
961
+ >,
962
+ ) {
963
+ return new Route<
964
+ TParentRoute,
965
+ TPath,
966
+ TFullPath,
967
+ TCustomId,
968
+ TId,
969
+ TSearchSchemaInput,
970
+ TSearchSchema,
971
+ TSearchSchemaUsed,
972
+ TFullSearchSchemaInput,
973
+ TFullSearchSchema,
974
+ TParams,
975
+ TAllParams,
976
+ TRouteContextReturn,
977
+ TRouteContext,
978
+ TAllContext,
979
+ TRouterContext,
980
+ TLoaderDeps,
981
+ TLoaderData,
982
+ TChildren,
983
+ TRouteTree
984
+ >(options)
985
+ }
986
+
841
987
  export type AnyRootRoute = RootRoute<any, any, any, any, any, any, any, any>
842
988
 
843
989
  export function rootRouteWithContext<TRouterContext extends {}>() {
@@ -878,15 +1024,15 @@ export function rootRouteWithContext<TRouterContext extends {}>() {
878
1024
  | 'parseParams'
879
1025
  | 'stringifyParams'
880
1026
  >,
881
- ): RootRoute<
882
- TSearchSchemaInput,
883
- TSearchSchema,
884
- TSearchSchemaUsed,
885
- TRouteContextReturn,
886
- TRouteContext,
887
- TRouterContext
888
- > => {
889
- return new RootRoute(options) as any
1027
+ ) => {
1028
+ return createRootRoute<
1029
+ TSearchSchemaInput,
1030
+ TSearchSchema,
1031
+ TSearchSchemaUsed,
1032
+ TRouteContextReturn,
1033
+ TRouteContext,
1034
+ TRouterContext
1035
+ >(options as any)
890
1036
  }
891
1037
  }
892
1038
 
@@ -894,6 +1040,9 @@ export type RootSearchSchema = {
894
1040
  __TRootSearchSchema__: '__TRootSearchSchema__'
895
1041
  }
896
1042
 
1043
+ /**
1044
+ * @deprecated `RootRoute` is now an internal implementation detail. Use `createRootRoute()` instead.
1045
+ */
897
1046
  export class RootRoute<
898
1047
  TSearchSchemaInput extends Record<string, any> = RootSearchSchema,
899
1048
  TSearchSchema extends Record<string, any> = RootSearchSchema,
@@ -959,6 +1108,57 @@ export class RootRoute<
959
1108
  }
960
1109
  }
961
1110
 
1111
+ export function createRootRoute<
1112
+ TSearchSchemaInput extends Record<string, any> = RootSearchSchema,
1113
+ TSearchSchema extends Record<string, any> = RootSearchSchema,
1114
+ TSearchSchemaUsed extends Record<string, any> = RootSearchSchema,
1115
+ TRouteContextReturn extends RouteContext = RouteContext,
1116
+ TRouteContext extends RouteContext = [TRouteContextReturn] extends [never]
1117
+ ? RouteContext
1118
+ : TRouteContextReturn,
1119
+ TRouterContext extends {} = {},
1120
+ TLoaderDeps extends Record<string, any> = {},
1121
+ TLoaderData extends any = unknown,
1122
+ >(
1123
+ options?: Omit<
1124
+ RouteOptions<
1125
+ AnyRoute, // TParentRoute
1126
+ RootRouteId, // TCustomId
1127
+ '', // TPath
1128
+ TSearchSchemaInput, // TSearchSchemaInput
1129
+ TSearchSchema, // TSearchSchema
1130
+ TSearchSchemaUsed,
1131
+ TSearchSchemaUsed, // TFullSearchSchemaInput
1132
+ TSearchSchema, // TFullSearchSchema
1133
+ {}, // TParams
1134
+ {}, // TAllParams
1135
+ TRouteContextReturn, // TRouteContextReturn
1136
+ TRouteContext, // TRouteContext
1137
+ TRouterContext,
1138
+ Assign<TRouterContext, TRouteContext>, // TAllContext
1139
+ TLoaderDeps,
1140
+ TLoaderData
1141
+ >,
1142
+ | 'path'
1143
+ | 'id'
1144
+ | 'getParentRoute'
1145
+ | 'caseSensitive'
1146
+ | 'parseParams'
1147
+ | 'stringifyParams'
1148
+ >,
1149
+ ) {
1150
+ return new RootRoute<
1151
+ TSearchSchemaInput,
1152
+ TSearchSchema,
1153
+ TSearchSchemaUsed,
1154
+ TRouteContextReturn,
1155
+ TRouteContext,
1156
+ TRouterContext,
1157
+ TLoaderDeps,
1158
+ TLoaderData
1159
+ >(options)
1160
+ }
1161
+
962
1162
  export type ResolveFullPath<
963
1163
  TParentRoute extends AnyRoute,
964
1164
  TPath extends string,
@@ -1019,10 +1219,18 @@ export function createRouteMask<
1019
1219
  return opts as any
1020
1220
  }
1021
1221
 
1222
+ /**
1223
+ * @deprecated Use `ErrorComponentProps` instead.
1224
+ */
1022
1225
  export type ErrorRouteProps = {
1023
1226
  error: unknown
1024
1227
  info: { componentStack: string }
1025
1228
  }
1229
+
1230
+ export type ErrorComponentProps = {
1231
+ error: unknown
1232
+ info: { componentStack: string }
1233
+ }
1026
1234
  //
1027
1235
 
1028
1236
  export type ReactNode = any
@@ -1038,7 +1246,7 @@ export type AsyncRouteComponent<TProps> = SyncRouteComponent<TProps> & {
1038
1246
  export type RouteComponent<TProps = any> = SyncRouteComponent<TProps> &
1039
1247
  AsyncRouteComponent<TProps>
1040
1248
 
1041
- export type ErrorRouteComponent = RouteComponent<ErrorRouteProps>
1249
+ export type ErrorRouteComponent = RouteComponent<ErrorComponentProps>
1042
1250
 
1043
1251
  export class NotFoundRoute<
1044
1252
  TParentRoute extends AnyRootRoute,
package/src/router.ts CHANGED
@@ -176,7 +176,7 @@ export interface DehydratedRouterState {
176
176
 
177
177
  export type DehydratedRouteMatch = Pick<
178
178
  RouteMatch,
179
- 'id' | 'status' | 'updatedAt'
179
+ 'id' | 'status' | 'updatedAt' | 'loaderData'
180
180
  >
181
181
 
182
182
  export interface DehydratedRouter {
@@ -224,6 +224,19 @@ export type RouterListener<TRouterEvent extends RouterEvent> = {
224
224
  fn: ListenerFn<TRouterEvent>
225
225
  }
226
226
 
227
+ export function createRouter<
228
+ TRouteTree extends AnyRoute = AnyRoute,
229
+ TDehydrated extends Record<string, any> = Record<string, any>,
230
+ TSerializedError extends Record<string, any> = Record<string, any>,
231
+ >(
232
+ options: RouterConstructorOptions<TRouteTree, TDehydrated, TSerializedError>,
233
+ ) {
234
+ return new Router<TRouteTree, TDehydrated, TSerializedError>(options)
235
+ }
236
+
237
+ /**
238
+ * @deprecated Use the `createRouter` function instead
239
+ */
227
240
  export class Router<
228
241
  TRouteTree extends AnyRoute = AnyRoute,
229
242
  TDehydrated extends Record<string, any> = Record<string, any>,
@@ -691,6 +704,7 @@ export class Router<
691
704
  // Create a fresh route match
692
705
  const hasLoaders = !!(
693
706
  route.options.loader ||
707
+ route.lazyFn ||
694
708
  componentTypes.some((d) => (route.options[d] as any)?.preload)
695
709
  )
696
710
 
@@ -763,8 +777,8 @@ export class Router<
763
777
  this.latestLocation.pathname,
764
778
  fromSearch,
765
779
  )
766
- const stayingMatches = matches?.filter(
767
- (d) => fromMatches?.find((e) => e.routeId === d.routeId),
780
+ const stayingMatches = matches?.filter((d) =>
781
+ fromMatches?.find((e) => e.routeId === d.routeId),
768
782
  )
769
783
 
770
784
  const prevParams = { ...last(fromMatches)?.params }
@@ -1142,6 +1156,9 @@ export class Router<
1142
1156
  ...beforeLoadContext,
1143
1157
  }
1144
1158
 
1159
+ const links = route.options.links?.()
1160
+ const scripts = route.options.scripts?.()
1161
+
1145
1162
  matches[index] = match = {
1146
1163
  ...match,
1147
1164
  routeContext: replaceEqualDeep(
@@ -1151,6 +1168,8 @@ export class Router<
1151
1168
  context: replaceEqualDeep(match.context, context),
1152
1169
  abortController,
1153
1170
  pendingPromise,
1171
+ links,
1172
+ scripts,
1154
1173
  }
1155
1174
  } catch (err) {
1156
1175
  handleErrorAndRedirect(err, 'BEFORE_LOAD')
@@ -1234,21 +1253,33 @@ export class Router<
1234
1253
  fetchCount: match.fetchCount + 1,
1235
1254
  }
1236
1255
 
1237
- const componentsPromise = Promise.all(
1238
- componentTypes.map(async (type) => {
1239
- const component = route.options[type]
1240
-
1241
- if ((component as any)?.preload) {
1242
- await (component as any).preload()
1243
- }
1244
- }),
1256
+ const lazyPromise =
1257
+ route.lazyFn?.().then((lazyRoute) => {
1258
+ Object.assign(route.options, lazyRoute.options)
1259
+ }) || Promise.resolve()
1260
+
1261
+ // If for some reason lazy resolves more lazy components...
1262
+ // We'll wait for that before pre attempt to preload any
1263
+ // components themselves.
1264
+ const componentsPromise = lazyPromise.then(() =>
1265
+ Promise.all(
1266
+ componentTypes.map(async (type) => {
1267
+ const component = route.options[type]
1268
+
1269
+ if ((component as any)?.preload) {
1270
+ await (component as any).preload()
1271
+ }
1272
+ }),
1273
+ ),
1245
1274
  )
1246
1275
 
1276
+ // Kick off the loader!
1247
1277
  const loaderPromise = route.options.loader?.(loaderContext)
1248
1278
 
1249
1279
  loadPromise = Promise.all([
1250
1280
  componentsPromise,
1251
1281
  loaderPromise,
1282
+ lazyPromise,
1252
1283
  ]).then((d) => d[1])
1253
1284
  }
1254
1285
 
@@ -1273,6 +1304,10 @@ export class Router<
1273
1304
 
1274
1305
  if ((latestPromise = checkLatest())) return await latestPromise
1275
1306
 
1307
+ const meta = route.options.meta?.({
1308
+ loaderData,
1309
+ })
1310
+
1276
1311
  matches[index] = match = {
1277
1312
  ...match,
1278
1313
  error: undefined,
@@ -1281,6 +1316,7 @@ export class Router<
1281
1316
  updatedAt: Date.now(),
1282
1317
  loaderData,
1283
1318
  loadPromise: undefined,
1319
+ meta,
1284
1320
  }
1285
1321
  } catch (error) {
1286
1322
  if ((latestPromise = checkLatest())) return await latestPromise
@@ -1704,9 +1740,16 @@ export class Router<
1704
1740
  )
1705
1741
 
1706
1742
  if (dehydratedMatch) {
1743
+ const route = this.looseRoutesById[match.routeId]!
1744
+
1707
1745
  return {
1708
1746
  ...match,
1709
1747
  ...dehydratedMatch,
1748
+ meta: route.options.meta?.({
1749
+ loaderData: dehydratedMatch.loaderData,
1750
+ }),
1751
+ links: route.options.links?.(),
1752
+ scripts: route.options.scripts?.(),
1710
1753
  }
1711
1754
  }
1712
1755
  return match
@@ -1716,6 +1759,7 @@ export class Router<
1716
1759
  return {
1717
1760
  ...s,
1718
1761
  matches: matches as any,
1762
+ lastUpdated: Date.now(),
1719
1763
  }
1720
1764
  })
1721
1765
  }
@@ -1758,7 +1802,7 @@ export function getInitialRouterState(
1758
1802
  matches: [],
1759
1803
  pendingMatches: [],
1760
1804
  cachedMatches: [],
1761
- lastUpdated: Date.now(),
1805
+ lastUpdated: 0,
1762
1806
  }
1763
1807
  }
1764
1808