@tanstack/react-router 0.0.1-beta.222 → 0.0.1-beta.224

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.
@@ -36,9 +36,7 @@ import {
36
36
  } from './route'
37
37
  import {
38
38
  FullSearchSchema,
39
- ParseRoute,
40
39
  RouteById,
41
- RouteIds,
42
40
  RoutePaths,
43
41
  RoutesById,
44
42
  RoutesByPath,
@@ -64,6 +62,7 @@ import {
64
62
  escapeJSON,
65
63
  } from './utils'
66
64
  import { MatchRouteOptions } from './Matches'
65
+ import { AnyRouteMatch, RouteMatch } from './Matches'
67
66
 
68
67
  export interface CommitLocationOptions {
69
68
  replace?: boolean
@@ -78,10 +77,6 @@ export interface MatchLocation {
78
77
  from?: string
79
78
  }
80
79
 
81
- type LinkCurrentTargetElement = {
82
- preloadTimeout?: null | ReturnType<typeof setTimeout>
83
- }
84
-
85
80
  export type BuildLinkFn<TRouteTree extends AnyRoute> = <
86
81
  TFrom extends RoutePaths<TRouteTree> = '/',
87
82
  TTo extends string = '',
@@ -119,43 +114,37 @@ export type BuildLocationFn<TRouteTree extends AnyRoute> = (
119
114
 
120
115
  export type InjectedHtmlEntry = string | (() => Promise<string> | string)
121
116
 
122
- export type RouterContext<
123
- TRouteTree extends AnyRoute,
124
- // TDehydrated extends Record<string, any>,
125
- > = {
126
- buildLink: BuildLinkFn<TRouteTree>
127
- state: RouterState<TRouteTree>
128
- navigate: NavigateFn<TRouteTree>
129
- matchRoute: MatchRouteFn<TRouteTree>
130
- routeTree: TRouteTree
131
- routesById: RoutesById<TRouteTree>
132
- options: RouterOptions<TRouteTree>
133
- history: RouterHistory
134
- load: LoadFn
135
- buildLocation: BuildLocationFn<TRouteTree>
136
- subscribe: Router<TRouteTree>['subscribe']
137
- resetNextScrollRef: React.MutableRefObject<boolean>
138
- injectedHtmlRef: React.MutableRefObject<InjectedHtmlEntry[]>
139
- injectHtml: (entry: InjectedHtmlEntry) => void
140
- dehydrateData: <T>(
141
- key: any,
142
- getData: T | (() => Promise<T> | T),
143
- ) => () => void
144
- hydrateData: <T>(key: any) => T | undefined
145
- }
146
-
147
- export const routerContext = React.createContext<RouterContext<any>>(null!)
117
+ // export type RouterContext<
118
+ // TRouteTree extends AnyRoute,
119
+ // // TDehydrated extends Record<string, any>,
120
+ // > = {
121
+ // buildLink: BuildLinkFn<TRouteTree>
122
+ // state: RouterState<TRouteTree>
123
+ // navigate: NavigateFn<TRouteTree>
124
+ // matchRoute: MatchRouteFn<TRouteTree>
125
+ // routeTree: TRouteTree
126
+ // routesById: RoutesById<TRouteTree>
127
+ // options: RouterOptions<TRouteTree>
128
+ // history: RouterHistory
129
+ // load: LoadFn
130
+ // buildLocation: BuildLocationFn<TRouteTree>
131
+ // subscribe: Router<TRouteTree>['subscribe']
132
+ // resetNextScrollRef: React.MutableRefObject<boolean>
133
+ // injectedHtmlRef: React.MutableRefObject<InjectedHtmlEntry[]>
134
+ // injectHtml: (entry: InjectedHtmlEntry) => void
135
+ // dehydrateData: <T>(
136
+ // key: any,
137
+ // getData: T | (() => Promise<T> | T),
138
+ // ) => () => void
139
+ // hydrateData: <T>(key: any) => T | undefined
140
+ // }
141
+
142
+ export const routerContext = React.createContext<Router<any>>(null!)
148
143
 
149
144
  if (typeof document !== 'undefined') {
150
145
  window.__TSR_ROUTER_CONTEXT__ = routerContext as any
151
146
  }
152
147
 
153
- const preloadWarning = 'Error preloading route! ☝️'
154
-
155
- function isCtrlEvent(e: MouseEvent) {
156
- return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey)
157
- }
158
-
159
148
  export class SearchParamError extends Error {}
160
149
 
161
150
  export class PathParamError extends Error {}
@@ -177,1198 +166,41 @@ export function RouterProvider<
177
166
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
178
167
  TDehydrated extends Record<string, any> = Record<string, any>,
179
168
  >({ router, ...rest }: RouterProps<TRouteTree, TDehydrated>) {
180
- const options = {
169
+ // Allow the router to update options on the router instance
170
+ router.updateOptions({
181
171
  ...router.options,
182
172
  ...rest,
183
173
  context: {
184
174
  ...router.options.context,
185
175
  ...rest?.context,
186
176
  },
187
- } as PickAsRequired<
188
- RouterOptions<TRouteTree, TDehydrated>,
189
- 'stringifySearch' | 'parseSearch' | 'context'
190
- >
191
-
192
- const history = React.useState(
193
- () => options.history ?? createBrowserHistory(),
194
- )[0]
195
-
196
- const tempLocationKeyRef = React.useRef<string | undefined>(
197
- `${Math.round(Math.random() * 10000000)}`,
198
- )
199
- const resetNextScrollRef = React.useRef<boolean>(true)
200
- const navigateTimeoutRef = React.useRef<NodeJS.Timeout | null>(null)
201
- const latestLoadPromiseRef = React.useRef<Promise<void>>(Promise.resolve())
202
-
203
- const checkLatest = (promise: Promise<void>): undefined | Promise<void> => {
204
- return latestLoadPromiseRef.current !== promise
205
- ? latestLoadPromiseRef.current
206
- : undefined
207
- }
177
+ } as PickAsRequired<RouterOptions<TRouteTree, TDehydrated>, 'stringifySearch' | 'parseSearch' | 'context'>)
208
178
 
209
- const parseLocation = useStableCallback(
210
- (
211
- previousLocation?: ParsedLocation,
212
- ): ParsedLocation<FullSearchSchema<TRouteTree>> => {
213
- const parse = ({
214
- pathname,
215
- search,
216
- hash,
217
- state,
218
- }: HistoryLocation): ParsedLocation<FullSearchSchema<TRouteTree>> => {
219
- const parsedSearch = options.parseSearch(search)
220
-
221
- return {
222
- pathname: pathname,
223
- searchStr: search,
224
- search: replaceEqualDeep(
225
- previousLocation?.search,
226
- parsedSearch,
227
- ) as any,
228
- hash: hash.split('#').reverse()[0] ?? '',
229
- href: `${pathname}${search}${hash}`,
230
- state: replaceEqualDeep(
231
- previousLocation?.state,
232
- state,
233
- ) as HistoryState,
234
- }
235
- }
236
-
237
- const location = parse(history.location)
238
-
239
- let { __tempLocation, __tempKey } = location.state
240
-
241
- if (
242
- __tempLocation &&
243
- (!__tempKey || __tempKey === tempLocationKeyRef.current)
244
- ) {
245
- // Sync up the location keys
246
- const parsedTempLocation = parse(__tempLocation) as any
247
- parsedTempLocation.state.key = location.state.key
248
-
249
- delete parsedTempLocation.state.__tempLocation
250
-
251
- return {
252
- ...parsedTempLocation,
253
- maskedLocation: location,
254
- }
255
- }
256
-
257
- return location
258
- },
259
- )
260
-
261
- const latestLocationRef = React.useRef<ParsedLocation>(parseLocation())
262
- const [preState, setState] = React.useState<RouterState<TRouteTree>>(() =>
263
- getInitialRouterState(latestLocationRef.current),
264
- )
179
+ const [preState, setState] = React.useState(() => router.state)
265
180
  const [isTransitioning, startReactTransition] = React.useTransition()
266
- const pendingMatchesRef = React.useRef<AnyRouteMatch[]>([])
267
181
 
268
182
  const state = React.useMemo<RouterState<TRouteTree>>(
269
183
  () => ({
270
184
  ...preState,
271
185
  status: isTransitioning ? 'pending' : 'idle',
272
- location: isTransitioning ? latestLocationRef.current : preState.location,
273
- pendingMatches: pendingMatchesRef.current,
186
+ location: isTransitioning ? router.latestLocation : preState.location,
187
+ pendingMatches: router.pendingMatches,
274
188
  }),
275
189
  [preState, isTransitioning],
276
190
  )
277
191
 
278
- React.useLayoutEffect(() => {
279
- if (!isTransitioning && state.resolvedLocation !== state.location) {
280
- router.emit({
281
- type: 'onResolved',
282
- fromLocation: state.resolvedLocation,
283
- toLocation: state.location,
284
- pathChanged: state.location!.href !== state.resolvedLocation?.href,
285
- })
286
- pendingMatchesRef.current = []
287
-
288
- setState((s) => ({
289
- ...s,
290
- resolvedLocation: s.location,
291
- }))
292
- }
293
- })
294
-
295
- const basepath = `/${trimPath(options.basepath ?? '') ?? ''}`
296
-
297
- const resolvePathWithBase = useStableCallback(
298
- (from: string, path: string) => {
299
- return resolvePath(basepath!, from, cleanPath(path))
300
- },
301
- )
302
-
303
- const [routesById, routesByPath] = React.useMemo(() => {
304
- const routesById = {} as RoutesById<TRouteTree>
305
- const routesByPath = {} as RoutesByPath<TRouteTree>
306
-
307
- const recurseRoutes = (routes: AnyRoute[]) => {
308
- routes.forEach((route, i) => {
309
- route.init({ originalIndex: i })
310
-
311
- const existingRoute = (routesById as any)[route.id]
312
-
313
- invariant(
314
- !existingRoute,
315
- `Duplicate routes found with id: ${String(route.id)}`,
316
- )
317
- ;(routesById as any)[route.id] = route
318
-
319
- if (!route.isRoot && route.path) {
320
- const trimmedFullPath = trimPathRight(route.fullPath)
321
- if (
322
- !(routesByPath as any)[trimmedFullPath] ||
323
- route.fullPath.endsWith('/')
324
- ) {
325
- ;(routesByPath as any)[trimmedFullPath] = route
326
- }
327
- }
328
-
329
- const children = route.children as Route[]
330
-
331
- if (children?.length) {
332
- recurseRoutes(children)
333
- }
334
- })
335
- }
336
-
337
- recurseRoutes([router.routeTree])
338
-
339
- return [routesById, routesByPath] as const
340
- }, [])
341
-
342
- const looseRoutesById = routesById as Record<string, AnyRoute>
343
-
344
- const flatRoutes = React.useMemo(
345
- () =>
346
- (Object.values(routesByPath) as AnyRoute[])
347
- .map((d, i) => {
348
- const trimmed = trimPath(d.fullPath)
349
- const parsed = parsePathname(trimmed)
350
-
351
- while (parsed.length > 1 && parsed[0]?.value === '/') {
352
- parsed.shift()
353
- }
354
-
355
- const score = parsed.map((d) => {
356
- if (d.type === 'param') {
357
- return 0.5
358
- }
359
-
360
- if (d.type === 'wildcard') {
361
- return 0.25
362
- }
363
-
364
- return 1
365
- })
366
-
367
- return { child: d, trimmed, parsed, index: i, score }
368
- })
369
- .sort((a, b) => {
370
- let isIndex = a.trimmed === '/' ? 1 : b.trimmed === '/' ? -1 : 0
371
-
372
- if (isIndex !== 0) return isIndex
373
-
374
- const length = Math.min(a.score.length, b.score.length)
375
-
376
- // Sort by length of score
377
- if (a.score.length !== b.score.length) {
378
- return b.score.length - a.score.length
379
- }
380
-
381
- // Sort by min available score
382
- for (let i = 0; i < length; i++) {
383
- if (a.score[i] !== b.score[i]) {
384
- return b.score[i]! - a.score[i]!
385
- }
386
- }
387
-
388
- // Sort by min available parsed value
389
- for (let i = 0; i < length; i++) {
390
- if (a.parsed[i]!.value !== b.parsed[i]!.value) {
391
- return a.parsed[i]!.value! > b.parsed[i]!.value! ? 1 : -1
392
- }
393
- }
394
-
395
- // Sort by length of trimmed full path
396
- if (a.trimmed !== b.trimmed) {
397
- return a.trimmed > b.trimmed ? 1 : -1
398
- }
399
-
400
- // Sort by original index
401
- return a.index - b.index
402
- })
403
- .map((d, i) => {
404
- d.child.rank = i
405
- return d.child
406
- }),
407
- [routesByPath],
408
- )
409
-
410
- const matchRoutes = useStableCallback(
411
- <TRouteTree extends AnyRoute>(
412
- pathname: string,
413
- locationSearch: AnySearchSchema,
414
- opts?: { throwOnError?: boolean; debug?: boolean },
415
- ): RouteMatch<TRouteTree>[] => {
416
- let routeParams: AnyPathParams = {}
417
-
418
- let foundRoute = flatRoutes.find((route) => {
419
- const matchedParams = matchPathname(basepath, trimPathRight(pathname), {
420
- to: route.fullPath,
421
- caseSensitive: route.options.caseSensitive ?? options.caseSensitive,
422
- fuzzy: false,
423
- })
424
-
425
- if (matchedParams) {
426
- routeParams = matchedParams
427
- return true
428
- }
429
-
430
- return false
431
- })
432
-
433
- let routeCursor: AnyRoute = foundRoute || (routesById as any)['__root__']
434
-
435
- let matchedRoutes: AnyRoute[] = [routeCursor]
436
- // let includingLayouts = true
437
- while (routeCursor?.parentRoute) {
438
- routeCursor = routeCursor.parentRoute
439
- if (routeCursor) matchedRoutes.unshift(routeCursor)
440
- }
441
-
442
- // Existing matches are matches that are already loaded along with
443
- // pending matches that are still loading
444
-
445
- const parseErrors = matchedRoutes.map((route) => {
446
- let parsedParamsError
447
-
448
- if (route.options.parseParams) {
449
- try {
450
- const parsedParams = route.options.parseParams(routeParams)
451
- // Add the parsed params to the accumulated params bag
452
- Object.assign(routeParams, parsedParams)
453
- } catch (err: any) {
454
- parsedParamsError = new PathParamError(err.message, {
455
- cause: err,
456
- })
457
-
458
- if (opts?.throwOnError) {
459
- throw parsedParamsError
460
- }
461
-
462
- return parsedParamsError
463
- }
464
- }
465
-
466
- return
467
- })
468
-
469
- const matches = matchedRoutes.map((route, index) => {
470
- const interpolatedPath = interpolatePath(route.path, routeParams)
471
- const matchId = interpolatePath(route.id, routeParams, true)
472
-
473
- // Waste not, want not. If we already have a match for this route,
474
- // reuse it. This is important for layout routes, which might stick
475
- // around between navigation actions that only change leaf routes.
476
- const existingMatch = getRouteMatch(state, matchId)
477
-
478
- if (existingMatch) {
479
- return { ...existingMatch }
480
- }
481
-
482
- // Create a fresh route match
483
- const hasLoaders = !!(
484
- route.options.loader ||
485
- componentTypes.some((d) => (route.options[d] as any)?.preload)
486
- )
487
-
488
- const routeMatch: AnyRouteMatch = {
489
- id: matchId,
490
- routeId: route.id,
491
- params: routeParams,
492
- pathname: joinPaths([basepath, interpolatedPath]),
493
- updatedAt: Date.now(),
494
- routeSearch: {},
495
- search: {} as any,
496
- status: hasLoaders ? 'pending' : 'success',
497
- isFetching: false,
498
- invalid: false,
499
- error: undefined,
500
- paramsError: parseErrors[index],
501
- searchError: undefined,
502
- loadPromise: Promise.resolve(),
503
- context: undefined!,
504
- abortController: new AbortController(),
505
- shouldReloadDeps: undefined,
506
- fetchedAt: 0,
507
- }
508
-
509
- return routeMatch
510
- })
511
-
512
- // Take each match and resolve its search params and context
513
- // This has to happen after the matches are created or found
514
- // so that we can use the parent match's search params and context
515
- matches.forEach((match, i): any => {
516
- const parentMatch = matches[i - 1]
517
- const route = looseRoutesById[match.routeId]!
518
-
519
- const searchInfo = (() => {
520
- // Validate the search params and stabilize them
521
- const parentSearchInfo = {
522
- search: parentMatch?.search ?? locationSearch,
523
- routeSearch: parentMatch?.routeSearch ?? locationSearch,
524
- }
525
-
526
- try {
527
- const validator =
528
- typeof route.options.validateSearch === 'object'
529
- ? route.options.validateSearch.parse
530
- : route.options.validateSearch
531
-
532
- let routeSearch = validator?.(parentSearchInfo.search) ?? {}
533
-
534
- let search = {
535
- ...parentSearchInfo.search,
536
- ...routeSearch,
537
- }
538
-
539
- routeSearch = replaceEqualDeep(match.routeSearch, routeSearch)
540
- search = replaceEqualDeep(match.search, search)
541
-
542
- return {
543
- routeSearch,
544
- search,
545
- searchDidChange: match.routeSearch !== routeSearch,
546
- }
547
- } catch (err: any) {
548
- match.searchError = new SearchParamError(err.message, {
549
- cause: err,
550
- })
551
-
552
- if (opts?.throwOnError) {
553
- throw match.searchError
554
- }
555
-
556
- return parentSearchInfo
557
- }
558
- })()
559
-
560
- Object.assign(match, searchInfo)
561
- })
562
-
563
- return matches as any
564
- },
565
- )
566
-
567
- const cancelMatch = useStableCallback(
568
- <TRouteTree extends AnyRoute>(id: string) => {
569
- getRouteMatch(state, id)?.abortController?.abort()
570
- },
571
- )
572
-
573
- const cancelMatches = useStableCallback(
574
- <TRouteTree extends AnyRoute>(state: RouterState<TRouteTree>) => {
575
- state.matches.forEach((match) => {
576
- cancelMatch(match.id)
577
- })
578
- },
579
- )
580
-
581
- const buildLocation = useStableCallback<BuildLocationFn<TRouteTree>>(
582
- (opts) => {
583
- const build = (
584
- dest: BuildNextOptions & {
585
- unmaskOnReload?: boolean
586
- } = {},
587
- matches?: AnyRouteMatch[],
588
- ): ParsedLocation => {
589
- const from = latestLocationRef.current
590
- const fromPathname = dest.from ?? from.pathname
591
-
592
- let pathname = resolvePathWithBase(fromPathname, `${dest.to ?? ''}`)
593
-
594
- const fromMatches = matchRoutes(fromPathname, from.search)
595
- const stayingMatches = matches?.filter((d) =>
596
- fromMatches?.find((e) => e.routeId === d.routeId),
597
- )
598
-
599
- const prevParams = { ...last(fromMatches)?.params }
600
-
601
- let nextParams =
602
- (dest.params ?? true) === true
603
- ? prevParams
604
- : functionalUpdate(dest.params!, prevParams)
605
-
606
- if (nextParams) {
607
- matches
608
- ?.map((d) => looseRoutesById[d.routeId]!.options.stringifyParams)
609
- .filter(Boolean)
610
- .forEach((fn) => {
611
- nextParams = { ...nextParams!, ...fn!(nextParams!) }
612
- })
613
- }
614
-
615
- pathname = interpolatePath(pathname, nextParams ?? {})
616
-
617
- const preSearchFilters =
618
- stayingMatches
619
- ?.map(
620
- (match) =>
621
- looseRoutesById[match.routeId]!.options.preSearchFilters ?? [],
622
- )
623
- .flat()
624
- .filter(Boolean) ?? []
625
-
626
- const postSearchFilters =
627
- stayingMatches
628
- ?.map(
629
- (match) =>
630
- looseRoutesById[match.routeId]!.options.postSearchFilters ?? [],
631
- )
632
- .flat()
633
- .filter(Boolean) ?? []
634
-
635
- // Pre filters first
636
- const preFilteredSearch = preSearchFilters?.length
637
- ? preSearchFilters?.reduce(
638
- (prev, next) => next(prev) as any,
639
- from.search,
640
- )
641
- : from.search
642
-
643
- // Then the link/navigate function
644
- const destSearch =
645
- dest.search === true
646
- ? preFilteredSearch // Preserve resolvedFrom true
647
- : dest.search
648
- ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
649
- : preSearchFilters?.length
650
- ? preFilteredSearch // Preserve resolvedFrom filters
651
- : {}
652
-
653
- // Then post filters
654
- const postFilteredSearch = postSearchFilters?.length
655
- ? postSearchFilters.reduce((prev, next) => next(prev), destSearch)
656
- : destSearch
657
-
658
- const search = replaceEqualDeep(from.search, postFilteredSearch)
659
-
660
- const searchStr = options.stringifySearch(search)
661
-
662
- const hash =
663
- dest.hash === true
664
- ? from.hash
665
- : dest.hash
666
- ? functionalUpdate(dest.hash!, from.hash)
667
- : from.hash
668
-
669
- const hashStr = hash ? `#${hash}` : ''
670
-
671
- let nextState =
672
- dest.state === true
673
- ? from.state
674
- : dest.state
675
- ? functionalUpdate(dest.state, from.state)
676
- : from.state
677
-
678
- nextState = replaceEqualDeep(from.state, nextState)
679
-
680
- return {
681
- pathname,
682
- search,
683
- searchStr,
684
- state: nextState as any,
685
- hash,
686
- href: history.createHref(`${pathname}${searchStr}${hashStr}`),
687
- unmaskOnReload: dest.unmaskOnReload,
688
- }
689
- }
690
-
691
- const buildWithMatches = (
692
- dest: BuildNextOptions = {},
693
- maskedDest?: BuildNextOptions,
694
- ) => {
695
- let next = build(dest)
696
- let maskedNext = maskedDest ? build(maskedDest) : undefined
697
-
698
- if (!maskedNext) {
699
- let params = {}
700
-
701
- let foundMask = options.routeMasks?.find((d) => {
702
- const match = matchPathname(basepath, next.pathname, {
703
- to: d.from,
704
- caseSensitive: false,
705
- fuzzy: false,
706
- })
707
-
708
- if (match) {
709
- params = match
710
- return true
711
- }
712
-
713
- return false
714
- })
715
-
716
- if (foundMask) {
717
- foundMask = {
718
- ...foundMask,
719
- from: interpolatePath(foundMask.from, params) as any,
720
- }
721
- maskedDest = foundMask
722
- maskedNext = build(maskedDest)
723
- }
724
- }
725
-
726
- const nextMatches = matchRoutes(next.pathname, next.search)
727
- const maskedMatches = maskedNext
728
- ? matchRoutes(maskedNext.pathname, maskedNext.search)
729
- : undefined
730
- const maskedFinal = maskedNext
731
- ? build(maskedDest, maskedMatches)
732
- : undefined
733
-
734
- const final = build(dest, nextMatches)
735
-
736
- if (maskedFinal) {
737
- final.maskedLocation = maskedFinal
738
- }
739
-
740
- return final
741
- }
742
-
743
- if (opts.mask) {
744
- return buildWithMatches(opts, {
745
- ...pick(opts, ['from']),
746
- ...opts.mask,
747
- })
748
- }
749
-
750
- return buildWithMatches(opts)
751
- },
752
- )
753
-
754
- const commitLocation = useStableCallback(
755
- async ({
756
- startTransition,
757
- ...next
758
- }: ParsedLocation & CommitLocationOptions) => {
759
- if (navigateTimeoutRef.current) clearTimeout(navigateTimeoutRef.current)
760
-
761
- const isSameUrl = latestLocationRef.current.href === next.href
762
-
763
- // If the next urls are the same and we're not replacing,
764
- // do nothing
765
- if (!isSameUrl || !next.replace) {
766
- let { maskedLocation, ...nextHistory } = next
767
-
768
- if (maskedLocation) {
769
- nextHistory = {
770
- ...maskedLocation,
771
- state: {
772
- ...maskedLocation.state,
773
- __tempKey: undefined,
774
- __tempLocation: {
775
- ...nextHistory,
776
- search: nextHistory.searchStr,
777
- state: {
778
- ...nextHistory.state,
779
- __tempKey: undefined!,
780
- __tempLocation: undefined!,
781
- key: undefined!,
782
- },
783
- },
784
- },
785
- }
786
-
787
- if (nextHistory.unmaskOnReload ?? options.unmaskOnReload ?? false) {
788
- nextHistory.state.__tempKey = tempLocationKeyRef.current
789
- }
790
- }
791
-
792
- const apply = () => {
793
- history[next.replace ? 'replace' : 'push'](
794
- nextHistory.href,
795
- nextHistory.state,
796
- )
797
- }
798
-
799
- if (startTransition ?? true) {
800
- startReactTransition(apply)
801
- } else {
802
- apply()
803
- }
804
- }
805
-
806
- resetNextScrollRef.current = next.resetScroll ?? true
807
-
808
- return latestLoadPromiseRef.current
809
- },
810
- )
811
-
812
- const buildAndCommitLocation = useStableCallback(
813
- ({
814
- replace,
815
- resetScroll,
816
- startTransition,
817
- ...rest
818
- }: BuildNextOptions & CommitLocationOptions = {}) => {
819
- const location = buildLocation(rest)
820
- return commitLocation({
821
- ...location,
822
- startTransition,
823
- replace,
824
- resetScroll,
825
- })
826
- },
827
- )
828
-
829
- const navigate = useStableCallback<NavigateFn<TRouteTree>>(
830
- ({ from, to = '', ...rest }) => {
831
- // If this link simply reloads the current route,
832
- // make sure it has a new key so it will trigger a data refresh
833
-
834
- // If this `to` is a valid external URL, return
835
- // null for LinkUtils
836
- const toString = String(to)
837
- const fromString = typeof from === 'undefined' ? from : String(from)
838
- let isExternal
839
-
840
- try {
841
- new URL(`${toString}`)
842
- isExternal = true
843
- } catch (e) {}
844
-
845
- invariant(
846
- !isExternal,
847
- 'Attempting to navigate to external url with this.navigate!',
848
- )
849
-
850
- return buildAndCommitLocation({
851
- ...rest,
852
- from: fromString,
853
- to: toString,
854
- })
855
- },
856
- )
857
-
858
- const loadMatches = useStableCallback(
859
- async ({
860
- checkLatest,
861
- matches,
862
- preload,
863
- }: {
864
- checkLatest: () => Promise<void> | undefined
865
- matches: AnyRouteMatch[]
866
- preload?: boolean
867
- }): Promise<RouteMatch[]> => {
868
- let latestPromise
869
- let firstBadMatchIndex: number | undefined
870
-
871
- // Check each match middleware to see if the route can be accessed
872
- try {
873
- for (let [index, match] of matches.entries()) {
874
- const parentMatch = matches[index - 1]
875
- const route = looseRoutesById[match.routeId]!
876
-
877
- const handleError = (err: any, code: string) => {
878
- err.routerCode = code
879
- firstBadMatchIndex = firstBadMatchIndex ?? index
880
-
881
- if (isRedirect(err)) {
882
- throw err
883
- }
884
-
885
- try {
886
- route.options.onError?.(err)
887
- } catch (errorHandlerErr) {
888
- err = errorHandlerErr
889
-
890
- if (isRedirect(errorHandlerErr)) {
891
- throw errorHandlerErr
892
- }
893
- }
894
-
895
- matches[index] = match = {
896
- ...match,
897
- error: err,
898
- status: 'error',
899
- updatedAt: Date.now(),
900
- }
901
- }
902
-
903
- try {
904
- if (match.paramsError) {
905
- handleError(match.paramsError, 'PARSE_PARAMS')
906
- }
907
-
908
- if (match.searchError) {
909
- handleError(match.searchError, 'VALIDATE_SEARCH')
910
- }
911
-
912
- const parentContext = parentMatch?.context ?? options.context ?? {}
913
-
914
- const beforeLoadContext =
915
- (await route.options.beforeLoad?.({
916
- search: match.search,
917
- abortController: match.abortController,
918
- params: match.params,
919
- preload: !!preload,
920
- context: parentContext,
921
- location: state.location,
922
- navigate: (opts) =>
923
- navigate({ ...opts, from: match.pathname } as any),
924
- buildLocation,
925
- })) ?? ({} as any)
926
-
927
- const context = {
928
- ...parentContext,
929
- ...beforeLoadContext,
930
- }
931
-
932
- matches[index] = match = {
933
- ...match,
934
- context: replaceEqualDeep(match.context, context),
935
- }
936
- } catch (err) {
937
- handleError(err, 'BEFORE_LOAD')
938
- break
939
- }
940
- }
941
- } catch (err) {
942
- if (isRedirect(err)) {
943
- if (!preload) navigate(err as any)
944
- return matches
945
- }
946
-
947
- throw err
948
- }
949
-
950
- const validResolvedMatches = matches.slice(0, firstBadMatchIndex)
951
- const matchPromises: Promise<any>[] = []
952
-
953
- validResolvedMatches.forEach((match, index) => {
954
- matchPromises.push(
955
- (async () => {
956
- const parentMatchPromise = matchPromises[index - 1]
957
- const route = looseRoutesById[match.routeId]!
958
-
959
- const handleIfRedirect = (err: any) => {
960
- if (isRedirect(err)) {
961
- if (!preload) {
962
- navigate(err as any)
963
- }
964
- return true
965
- }
966
- return false
967
- }
968
-
969
- let loadPromise: Promise<void> | undefined
970
-
971
- matches[index] = match = {
972
- ...match,
973
- fetchedAt: Date.now(),
974
- invalid: false,
975
- }
976
-
977
- if (match.isFetching) {
978
- loadPromise = getRouteMatch(state, match.id)?.loadPromise
979
- } else {
980
- const cause = state.matches.find((d) => d.id === match.id)
981
- ? 'stay'
982
- : 'enter'
983
-
984
- const loaderContext: LoaderFnContext = {
985
- params: match.params,
986
- search: match.search,
987
- preload: !!preload,
988
- parentMatchPromise,
989
- abortController: match.abortController,
990
- context: match.context,
991
- location: state.location,
992
- navigate: (opts) =>
993
- navigate({ ...opts, from: match.pathname } as any),
994
- cause,
995
- }
996
-
997
- // Default to reloading the route all the time
998
- let shouldReload = true
999
-
1000
- let shouldReloadDeps =
1001
- typeof route.options.shouldReload === 'function'
1002
- ? route.options.shouldReload?.(loaderContext)
1003
- : !!(route.options.shouldReload ?? true)
1004
-
1005
- if (cause === 'enter') {
1006
- match.shouldReloadDeps = shouldReloadDeps
1007
- } else if (cause === 'stay') {
1008
- if (typeof shouldReloadDeps === 'object') {
1009
- // compare the deps to see if they've changed
1010
- shouldReload = !deepEqual(
1011
- shouldReloadDeps,
1012
- match.shouldReloadDeps,
1013
- )
1014
-
1015
- match.shouldReloadDeps = shouldReloadDeps
1016
- } else {
1017
- shouldReload = !!shouldReloadDeps
1018
- }
1019
- }
1020
-
1021
- // If the user doesn't want the route to reload, just
1022
- // resolve with the existing loader data
1023
-
1024
- if (!shouldReload) {
1025
- loadPromise = Promise.resolve(match.loaderData)
1026
- } else {
1027
- // Otherwise, load the route
1028
- matches[index] = match = {
1029
- ...match,
1030
- isFetching: true,
1031
- }
1032
-
1033
- const componentsPromise = Promise.all(
1034
- componentTypes.map(async (type) => {
1035
- const component = route.options[type]
1036
-
1037
- if ((component as any)?.preload) {
1038
- await (component as any).preload()
1039
- }
1040
- }),
1041
- )
1042
-
1043
- const loaderPromise = route.options.loader?.(loaderContext)
1044
-
1045
- loadPromise = Promise.all([
1046
- componentsPromise,
1047
- loaderPromise,
1048
- ]).then((d) => d[1])
1049
- }
1050
- }
1051
-
1052
- matches[index] = match = {
1053
- ...match,
1054
- loadPromise,
1055
- }
1056
-
1057
- if (!preload) {
1058
- setState((s) => ({
1059
- ...s,
1060
- matches: s.matches.map((d) => (d.id === match.id ? match : d)),
1061
- }))
1062
- }
1063
-
1064
- try {
1065
- const loaderData = await loadPromise
1066
- if ((latestPromise = checkLatest())) return await latestPromise
1067
-
1068
- matches[index] = match = {
1069
- ...match,
1070
- error: undefined,
1071
- status: 'success',
1072
- isFetching: false,
1073
- updatedAt: Date.now(),
1074
- loaderData,
1075
- loadPromise: undefined,
1076
- }
1077
- } catch (error) {
1078
- if ((latestPromise = checkLatest())) return await latestPromise
1079
- if (handleIfRedirect(error)) return
1080
-
1081
- try {
1082
- route.options.onError?.(error)
1083
- } catch (onErrorError) {
1084
- error = onErrorError
1085
- if (handleIfRedirect(onErrorError)) return
1086
- }
1087
-
1088
- matches[index] = match = {
1089
- ...match,
1090
- error,
1091
- status: 'error',
1092
- isFetching: false,
1093
- updatedAt: Date.now(),
1094
- }
1095
- }
1096
-
1097
- if (!preload) {
1098
- setState((s) => ({
1099
- ...s,
1100
- matches: s.matches.map((d) => (d.id === match.id ? match : d)),
1101
- }))
1102
- }
1103
- })(),
1104
- )
1105
- })
1106
-
1107
- await Promise.all(matchPromises)
1108
- return matches
1109
- },
1110
- )
1111
-
1112
- const load = useStableCallback<LoadFn>(async () => {
1113
- const promise = new Promise<void>(async (resolve, reject) => {
1114
- const next = latestLocationRef.current
1115
- const prevLocation = state.resolvedLocation
1116
- const pathDidChange = prevLocation!.href !== next.href
1117
- let latestPromise: Promise<void> | undefined | null
1118
-
1119
- // Cancel any pending matches
1120
- cancelMatches(state)
1121
-
1122
- router.emit({
1123
- type: 'onBeforeLoad',
1124
- fromLocation: prevLocation,
1125
- toLocation: next,
1126
- pathChanged: pathDidChange,
1127
- })
1128
-
1129
- // Match the routes
1130
- let matches: RouteMatch<any, any>[] = matchRoutes(
1131
- next.pathname,
1132
- next.search,
1133
- {
1134
- debug: true,
1135
- },
1136
- )
1137
-
1138
- pendingMatchesRef.current = matches
1139
-
1140
- const previousMatches = state.matches
1141
-
1142
- // Ingest the new matches
1143
- setState((s) => ({
1144
- ...s,
1145
- status: 'pending',
1146
- location: next,
1147
- matches,
1148
- }))
1149
-
1150
- try {
1151
- try {
1152
- // Load the matches
1153
- await loadMatches({
1154
- matches,
1155
- checkLatest: () => checkLatest(promise),
1156
- })
1157
- } catch (err) {
1158
- // swallow this error, since we'll display the
1159
- // errors on the route components
1160
- }
1161
-
1162
- // Only apply the latest transition
1163
- if ((latestPromise = checkLatest(promise))) {
1164
- return latestPromise
1165
- }
1166
-
1167
- const exitingMatchIds = previousMatches.filter(
1168
- (id) => !pendingMatchesRef.current.includes(id),
1169
- )
1170
- const enteringMatchIds = pendingMatchesRef.current.filter(
1171
- (id) => !previousMatches.includes(id),
1172
- )
1173
- const stayingMatchIds = previousMatches.filter((id) =>
1174
- pendingMatchesRef.current.includes(id),
1175
- )
1176
-
1177
- // setState((s) => ({
1178
- // ...s,
1179
- // status: 'idle',
1180
- // resolvedLocation: s.location,
1181
- // }))
1182
-
1183
- //
1184
- ;(
1185
- [
1186
- [exitingMatchIds, 'onLeave'],
1187
- [enteringMatchIds, 'onEnter'],
1188
- [stayingMatchIds, 'onTransition'],
1189
- ] as const
1190
- ).forEach(([matches, hook]) => {
1191
- matches.forEach((match) => {
1192
- looseRoutesById[match.routeId]!.options[hook]?.(match)
1193
- })
1194
- })
1195
-
1196
- router.emit({
1197
- type: 'onLoad',
1198
- fromLocation: prevLocation,
1199
- toLocation: next,
1200
- pathChanged: pathDidChange,
1201
- })
1202
-
1203
- resolve()
1204
- } catch (err) {
1205
- // Only apply the latest transition
1206
- if ((latestPromise = checkLatest(promise))) {
1207
- return latestPromise
1208
- }
1209
-
1210
- reject(err)
1211
- }
1212
- })
1213
-
1214
- latestLoadPromiseRef.current = promise
1215
-
1216
- return latestLoadPromiseRef.current
1217
- })
1218
-
1219
- const preloadRoute = useStableCallback(
1220
- async (navigateOpts: BuildNextOptions = state.location) => {
1221
- let next = buildLocation(navigateOpts)
1222
-
1223
- let matches = matchRoutes(next.pathname, next.search, {
1224
- throwOnError: true,
1225
- })
1226
-
1227
- await loadMatches({
1228
- matches,
1229
- preload: true,
1230
- checkLatest: () => undefined,
1231
- })
1232
-
1233
- return [last(matches)!, matches] as const
1234
- },
1235
- )
1236
-
1237
- const buildLink = useStableCallback<BuildLinkFn<TRouteTree>>((dest) => {
1238
- // If this link simply reloads the current route,
1239
- // make sure it has a new key so it will trigger a data refresh
1240
-
1241
- // If this `to` is a valid external URL, return
1242
- // null for LinkUtils
1243
-
1244
- const {
1245
- to,
1246
- preload: userPreload,
1247
- preloadDelay: userPreloadDelay,
1248
- activeOptions,
1249
- disabled,
1250
- target,
1251
- replace,
1252
- resetScroll,
1253
- startTransition,
1254
- } = dest
1255
-
1256
- try {
1257
- new URL(`${to}`)
1258
- return {
1259
- type: 'external',
1260
- href: to as any,
1261
- }
1262
- } catch (e) {}
1263
-
1264
- const nextOpts = dest
1265
- const next = buildLocation(nextOpts as any)
1266
-
1267
- const preload = userPreload ?? options.defaultPreload
1268
- const preloadDelay = userPreloadDelay ?? options.defaultPreloadDelay ?? 0
1269
-
1270
- // Compare path/hash for matches
1271
- const currentPathSplit = latestLocationRef.current.pathname.split('/')
1272
- const nextPathSplit = next.pathname.split('/')
1273
- const pathIsFuzzyEqual = nextPathSplit.every(
1274
- (d, i) => d === currentPathSplit[i],
1275
- )
1276
- // Combine the matches based on user options
1277
- const pathTest = activeOptions?.exact
1278
- ? latestLocationRef.current.pathname === next.pathname
1279
- : pathIsFuzzyEqual
1280
- const hashTest = activeOptions?.includeHash
1281
- ? latestLocationRef.current.hash === next.hash
1282
- : true
1283
- const searchTest =
1284
- activeOptions?.includeSearch ?? true
1285
- ? deepEqual(latestLocationRef.current.search, next.search, true)
1286
- : true
1287
-
1288
- // The final "active" test
1289
- const isActive = pathTest && hashTest && searchTest
1290
-
1291
- // The click handler
1292
- const handleClick = (e: MouseEvent) => {
1293
- if (
1294
- !disabled &&
1295
- !isCtrlEvent(e) &&
1296
- !e.defaultPrevented &&
1297
- (!target || target === '_self') &&
1298
- e.button === 0
1299
- ) {
1300
- e.preventDefault()
1301
-
1302
- // All is well? Navigate!
1303
- commitLocation({ ...next, replace, resetScroll, startTransition })
1304
- }
1305
- }
1306
-
1307
- // The click handler
1308
- const handleFocus = (e: MouseEvent) => {
1309
- if (preload) {
1310
- preloadRoute(nextOpts as any).catch((err) => {
1311
- console.warn(err)
1312
- console.warn(preloadWarning)
1313
- })
1314
- }
1315
- }
1316
-
1317
- const handleTouchStart = (e: TouchEvent) => {
1318
- preloadRoute(nextOpts as any).catch((err) => {
1319
- console.warn(err)
1320
- console.warn(preloadWarning)
1321
- })
1322
- }
1323
-
1324
- const handleEnter = (e: MouseEvent) => {
1325
- const target = (e.target || {}) as LinkCurrentTargetElement
1326
-
1327
- if (preload) {
1328
- if (target.preloadTimeout) {
1329
- return
1330
- }
1331
-
1332
- target.preloadTimeout = setTimeout(() => {
1333
- target.preloadTimeout = null
1334
- preloadRoute(nextOpts as any).catch((err) => {
1335
- console.warn(err)
1336
- console.warn(preloadWarning)
1337
- })
1338
- }, preloadDelay)
1339
- }
1340
- }
1341
-
1342
- const handleLeave = (e: MouseEvent) => {
1343
- const target = (e.target || {}) as LinkCurrentTargetElement
1344
-
1345
- if (target.preloadTimeout) {
1346
- clearTimeout(target.preloadTimeout)
1347
- target.preloadTimeout = null
1348
- }
1349
- }
1350
-
1351
- return {
1352
- type: 'internal',
1353
- next,
1354
- handleFocus,
1355
- handleClick,
1356
- handleEnter,
1357
- handleLeave,
1358
- handleTouchStart,
1359
- isActive,
1360
- disabled,
1361
- }
1362
- })
192
+ router.setState = setState
193
+ router.state = state
194
+ router.startReactTransition = startReactTransition
1363
195
 
1364
196
  React.useLayoutEffect(() => {
1365
- const unsub = history.subscribe(() => {
1366
- latestLocationRef.current = parseLocation(latestLocationRef.current)
197
+ const unsub = router.history.subscribe(() => {
198
+ router.latestLocation = router.parseLocation(router.latestLocation)
1367
199
 
1368
- if (state.location !== latestLocationRef.current) {
200
+ if (state.location !== router.latestLocation) {
1369
201
  startReactTransition(() => {
1370
202
  try {
1371
- load()
203
+ router.load()
1372
204
  } catch (err) {
1373
205
  console.error(err)
1374
206
  }
@@ -1376,7 +208,7 @@ export function RouterProvider<
1376
208
  }
1377
209
  })
1378
210
 
1379
- const nextLocation = buildLocation({
211
+ const nextLocation = router.buildLocation({
1380
212
  search: true,
1381
213
  params: true,
1382
214
  hash: true,
@@ -1384,7 +216,7 @@ export function RouterProvider<
1384
216
  })
1385
217
 
1386
218
  if (state.location.href !== nextLocation.href) {
1387
- commitLocation({ ...nextLocation, replace: true })
219
+ router.commitLocation({ ...nextLocation, replace: true })
1388
220
  }
1389
221
 
1390
222
  return () => {
@@ -1392,123 +224,35 @@ export function RouterProvider<
1392
224
  }
1393
225
  }, [history])
1394
226
 
1395
- const matchRoute = useStableCallback<MatchRouteFn<TRouteTree>>(
1396
- (location, opts) => {
1397
- location = {
1398
- ...location,
1399
- to: location.to
1400
- ? resolvePathWithBase((location.from || '') as string, location.to)
1401
- : undefined,
1402
- } as any
1403
-
1404
- const next = buildLocation(location as any)
1405
-
1406
- if (opts?.pending && state.status !== 'pending') {
1407
- return false
1408
- }
1409
-
1410
- const baseLocation = opts?.pending
1411
- ? latestLocationRef.current
1412
- : state.resolvedLocation
1413
-
1414
- // const baseLocation = state.resolvedLocation
1415
-
1416
- if (!baseLocation) {
1417
- return false
1418
- }
1419
-
1420
- const match = matchPathname(basepath, baseLocation.pathname, {
1421
- ...opts,
1422
- to: next.pathname,
1423
- }) as any
1424
-
1425
- if (!match) {
1426
- return false
1427
- }
1428
-
1429
- if (match && (opts?.includeSearch ?? true)) {
1430
- return deepEqual(baseLocation.search, next.search, true) ? match : false
1431
- }
1432
-
1433
- return match
1434
- },
1435
- )
1436
-
1437
- const injectedHtmlRef = React.useRef<InjectedHtmlEntry[]>([])
1438
-
1439
- const injectHtml = useStableCallback(
1440
- async (html: string | (() => Promise<string> | string)) => {
1441
- injectedHtmlRef.current.push(html)
1442
- },
1443
- )
1444
-
1445
- const dehydrateData = useStableCallback(
1446
- <T,>(key: any, getData: T | (() => Promise<T> | T)) => {
1447
- if (typeof document === 'undefined') {
1448
- const strKey = typeof key === 'string' ? key : JSON.stringify(key)
1449
-
1450
- injectHtml(async () => {
1451
- const id = `__TSR_DEHYDRATED__${strKey}`
1452
- const data =
1453
- typeof getData === 'function' ? await (getData as any)() : getData
1454
- return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(
1455
- strKey,
1456
- )}"] = ${JSON.stringify(data)}
1457
- ;(() => {
1458
- var el = document.getElementById('${id}')
1459
- el.parentElement.removeChild(el)
1460
- })()
1461
- </script>`
1462
- })
1463
-
1464
- return () => hydrateData<T>(key)
1465
- }
1466
-
1467
- return () => undefined
1468
- },
1469
- )
1470
-
1471
- const hydrateData = useStableCallback(<T extends any = unknown>(key: any) => {
1472
- if (typeof document !== 'undefined') {
1473
- const strKey = typeof key === 'string' ? key : JSON.stringify(key)
227
+ React.useLayoutEffect(() => {
228
+ if (!isTransitioning && state.resolvedLocation !== state.location) {
229
+ router.emit({
230
+ type: 'onResolved',
231
+ fromLocation: state.resolvedLocation,
232
+ toLocation: state.location,
233
+ pathChanged: state.location!.href !== state.resolvedLocation?.href,
234
+ })
235
+ router.pendingMatches = []
1474
236
 
1475
- return window[`__TSR_DEHYDRATED__${strKey}` as any] as T
237
+ setState((s) => ({
238
+ ...s,
239
+ resolvedLocation: s.location,
240
+ }))
1476
241
  }
1477
-
1478
- return undefined
1479
242
  })
1480
243
 
1481
244
  React.useLayoutEffect(() => {
1482
245
  startReactTransition(() => {
1483
246
  try {
1484
- load()
247
+ router.load()
1485
248
  } catch (err) {
1486
249
  console.error(err)
1487
250
  }
1488
251
  })
1489
252
  }, [])
1490
253
 
1491
- const routerContextValue: RouterContext<TRouteTree> = {
1492
- routeTree: router.routeTree,
1493
- navigate,
1494
- buildLink,
1495
- state,
1496
- matchRoute,
1497
- routesById,
1498
- options,
1499
- history,
1500
- load,
1501
- buildLocation,
1502
- subscribe: router.subscribe,
1503
- resetNextScrollRef,
1504
- injectedHtmlRef,
1505
- injectHtml,
1506
- dehydrateData,
1507
- hydrateData,
1508
- }
1509
-
1510
254
  return (
1511
- <routerContext.Provider value={routerContextValue}>
255
+ <routerContext.Provider value={router}>
1512
256
  <Matches />
1513
257
  </routerContext.Provider>
1514
258
  )
@@ -1541,37 +285,9 @@ export type RouterProps<
1541
285
 
1542
286
  export function useRouter<
1543
287
  TRouteTree extends AnyRoute = RegisteredRouter['routeTree'],
1544
- >(): RouterContext<TRouteTree> {
288
+ >(): Router<TRouteTree> {
1545
289
  const resolvedContext = window.__TSR_ROUTER_CONTEXT__ || routerContext
1546
290
  const value = React.useContext(resolvedContext)
1547
291
  warning(value, 'useRouter must be used inside a <RouterProvider> component!')
1548
292
  return value as any
1549
293
  }
1550
- export interface RouteMatch<
1551
- TRouteTree extends AnyRoute = AnyRoute,
1552
- TRouteId extends RouteIds<TRouteTree> = ParseRoute<TRouteTree>['id'],
1553
- > {
1554
- id: string
1555
- routeId: TRouteId
1556
- pathname: string
1557
- params: RouteById<TRouteTree, TRouteId>['types']['allParams']
1558
- status: 'pending' | 'success' | 'error'
1559
- isFetching: boolean
1560
- invalid: boolean
1561
- error: unknown
1562
- paramsError: unknown
1563
- searchError: unknown
1564
- updatedAt: number
1565
- loadPromise?: Promise<void>
1566
- loaderData?: RouteById<TRouteTree, TRouteId>['types']['loaderData']
1567
- __resolveLoadPromise?: () => void
1568
- context: RouteById<TRouteTree, TRouteId>['types']['allContext']
1569
- routeSearch: RouteById<TRouteTree, TRouteId>['types']['searchSchema']
1570
- search: FullSearchSchema<TRouteTree> &
1571
- RouteById<TRouteTree, TRouteId>['types']['fullSearchSchema']
1572
- fetchedAt: number
1573
- shouldReloadDeps: any
1574
- abortController: AbortController
1575
- }
1576
-
1577
- export type AnyRouteMatch = RouteMatch<any>