@tanstack/react-router 0.0.1-beta.225 → 0.0.1-beta.227

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/router.ts CHANGED
@@ -43,7 +43,6 @@ import {
43
43
  BuildLocationFn,
44
44
  CommitLocationOptions,
45
45
  InjectedHtmlEntry,
46
- LoadFn,
47
46
  MatchRouteFn,
48
47
  NavigateFn,
49
48
  PathParamError,
@@ -554,67 +553,21 @@ export class Router<
554
553
  return
555
554
  })
556
555
 
557
- const matches = matchedRoutes.map((route, index) => {
558
- const interpolatedPath = interpolatePath(route.path, routeParams)
559
- const matchId = interpolatePath(route.id, routeParams, true)
560
-
561
- // Waste not, want not. If we already have a match for this route,
562
- // reuse it. This is important for layout routes, which might stick
563
- // around between navigation actions that only change leaf routes.
564
- const existingMatch = getRouteMatch(this.state, matchId)
565
-
566
- const cause = this.state.matches.find((d) => d.id === matchId)
567
- ? 'stay'
568
- : 'enter'
569
-
570
- if (existingMatch) {
571
- return { ...existingMatch, cause }
572
- }
573
-
574
- // Create a fresh route match
575
- const hasLoaders = !!(
576
- route.options.loader ||
577
- componentTypes.some((d) => (route.options[d] as any)?.preload)
578
- )
556
+ const matches: AnyRouteMatch[] = []
579
557
 
580
- const routeMatch: AnyRouteMatch = {
581
- id: matchId,
582
- routeId: route.id,
583
- params: routeParams,
584
- pathname: joinPaths([this.basepath, interpolatedPath]),
585
- updatedAt: Date.now(),
586
- routeSearch: {},
587
- search: {} as any,
588
- status: hasLoaders ? 'pending' : 'success',
589
- isFetching: false,
590
- invalid: false,
591
- error: undefined,
592
- paramsError: parseErrors[index],
593
- searchError: undefined,
594
- loadPromise: Promise.resolve(),
595
- context: undefined!,
596
- abortController: new AbortController(),
597
- shouldReloadDeps: undefined,
598
- fetchedAt: 0,
599
- cause,
600
- }
558
+ matchedRoutes.forEach((route, index) => {
559
+ // Take each matched route and resolve + validate its search params
560
+ // This has to happen serially because each route's search params
561
+ // can depend on the parent route's search params
562
+ // It must also happen before we create the match so that we can
563
+ // pass the search params to the route's potential key function
564
+ // which is used to uniquely identify the route match in state
601
565
 
602
- return routeMatch
603
- })
566
+ const parentMatch = matches[index - 1]
604
567
 
605
- // Take each match and resolve its search params and context
606
- // This has to happen after the matches are created or found
607
- // so that we can use the parent match's search params and context
608
- matches.forEach((match, i): any => {
609
- const parentMatch = matches[i - 1]
610
- const route = this.looseRoutesById[match.routeId]!
611
-
612
- const searchInfo = (() => {
568
+ const [preMatchSearch, searchError]: [Record<string, any>, any] = (() => {
613
569
  // Validate the search params and stabilize them
614
- const parentSearchInfo = {
615
- search: parentMatch?.search ?? locationSearch,
616
- routeSearch: parentMatch?.routeSearch ?? locationSearch,
617
- }
570
+ const parentSearch = parentMatch?.search ?? locationSearch
618
571
 
619
572
  try {
620
573
  const validator =
@@ -622,35 +575,81 @@ export class Router<
622
575
  ? route.options.validateSearch.parse
623
576
  : route.options.validateSearch
624
577
 
625
- let routeSearch = validator?.(parentSearchInfo.search) ?? {}
626
-
627
- let search = {
628
- ...parentSearchInfo.search,
629
- ...routeSearch,
630
- }
631
-
632
- routeSearch = replaceEqualDeep(match.routeSearch, routeSearch)
633
- search = replaceEqualDeep(match.search, search)
578
+ let search = validator?.(parentSearch) ?? {}
634
579
 
635
- return {
636
- routeSearch,
637
- search,
638
- searchDidChange: match.routeSearch !== routeSearch,
639
- }
580
+ return [
581
+ {
582
+ ...parentSearch,
583
+ ...search,
584
+ },
585
+ undefined,
586
+ ]
640
587
  } catch (err: any) {
641
- match.searchError = new SearchParamError(err.message, {
588
+ const searchError = new SearchParamError(err.message, {
642
589
  cause: err,
643
590
  })
644
591
 
645
592
  if (opts?.throwOnError) {
646
- throw match.searchError
593
+ throw searchError
647
594
  }
648
595
 
649
- return parentSearchInfo
596
+ return [parentSearch, searchError]
650
597
  }
651
598
  })()
652
599
 
653
- Object.assign(match, searchInfo)
600
+ const interpolatedPath = interpolatePath(route.path, routeParams)
601
+ const matchId =
602
+ interpolatePath(route.id, routeParams, true) +
603
+ (route.options.key?.({
604
+ search: preMatchSearch,
605
+ location: this.state.location,
606
+ }) ?? '')
607
+
608
+ // Waste not, want not. If we already have a match for this route,
609
+ // reuse it. This is important for layout routes, which might stick
610
+ // around between navigation actions that only change leaf routes.
611
+ const existingMatch = getRouteMatch(this.state, matchId)
612
+
613
+ const cause = this.state.matches.find((d) => d.id === matchId)
614
+ ? 'stay'
615
+ : 'enter'
616
+
617
+ // Create a fresh route match
618
+ const hasLoaders = !!(
619
+ route.options.loader ||
620
+ componentTypes.some((d) => (route.options[d] as any)?.preload)
621
+ )
622
+
623
+ const match: AnyRouteMatch = existingMatch
624
+ ? { ...existingMatch, cause }
625
+ : {
626
+ id: matchId,
627
+ routeId: route.id,
628
+ params: routeParams,
629
+ pathname: joinPaths([this.basepath, interpolatedPath]),
630
+ updatedAt: Date.now(),
631
+ search: {} as any,
632
+ searchError: undefined,
633
+ status: hasLoaders ? 'pending' : 'success',
634
+ isFetching: false,
635
+ invalid: false,
636
+ error: undefined,
637
+ paramsError: parseErrors[index],
638
+ loadPromise: Promise.resolve(),
639
+ context: undefined!,
640
+ abortController: new AbortController(),
641
+ shouldReloadDeps: undefined,
642
+ fetchedAt: 0,
643
+ cause,
644
+ }
645
+
646
+ // Regardless of whether we're reusing an existing match or creating
647
+ // a new one, we need to update the match's search params
648
+ match.search = replaceEqualDeep(match.search, preMatchSearch)
649
+ // And also update the searchError if there is one
650
+ match.searchError = searchError
651
+
652
+ matches.push(match)
654
653
  })
655
654
 
656
655
  return matches as any
@@ -679,8 +678,8 @@ export class Router<
679
678
  let pathname = this.resolvePathWithBase(fromPathname, `${dest.to ?? ''}`)
680
679
 
681
680
  const fromMatches = this.matchRoutes(fromPathname, from.search)
682
- const stayingMatches = matches?.filter((d) =>
683
- fromMatches?.find((e) => e.routeId === d.routeId),
681
+ const stayingMatches = matches?.filter(
682
+ (d) => fromMatches?.find((e) => e.routeId === d.routeId),
684
683
  )
685
684
 
686
685
  const prevParams = { ...last(fromMatches)?.params }
@@ -734,10 +733,10 @@ export class Router<
734
733
  dest.search === true
735
734
  ? preFilteredSearch // Preserve resolvedFrom true
736
735
  : dest.search
737
- ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
738
- : preSearchFilters?.length
739
- ? preFilteredSearch // Preserve resolvedFrom filters
740
- : {}
736
+ ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
737
+ : preSearchFilters?.length
738
+ ? preFilteredSearch // Preserve resolvedFrom filters
739
+ : {}
741
740
 
742
741
  // Then post filters
743
742
  const postFilteredSearch = postSearchFilters?.length
@@ -752,8 +751,8 @@ export class Router<
752
751
  dest.hash === true
753
752
  ? from.hash
754
753
  : dest.hash
755
- ? functionalUpdate(dest.hash!, from.hash)
756
- : from.hash
754
+ ? functionalUpdate(dest.hash!, from.hash)
755
+ : from.hash
757
756
 
758
757
  const hashStr = hash ? `#${hash}` : ''
759
758
 
@@ -761,8 +760,8 @@ export class Router<
761
760
  dest.state === true
762
761
  ? from.state
763
762
  : dest.state
764
- ? functionalUpdate(dest.state, from.state)
765
- : from.state
763
+ ? functionalUpdate(dest.state, from.state)
764
+ : from.state
766
765
 
767
766
  nextState = replaceEqualDeep(from.state, nextState)
768
767
 
@@ -945,10 +944,12 @@ export class Router<
945
944
  checkLatest,
946
945
  matches,
947
946
  preload,
947
+ invalidate,
948
948
  }: {
949
949
  checkLatest: () => Promise<void> | undefined
950
950
  matches: AnyRouteMatch[]
951
951
  preload?: boolean
952
+ invalidate?: boolean
952
953
  }): Promise<RouteMatch[]> => {
953
954
  let latestPromise
954
955
  let firstBadMatchIndex: number | undefined
@@ -1086,7 +1087,7 @@ export class Router<
1086
1087
  ? route.options.shouldReload?.(loaderContext)
1087
1088
  : !!(route.options.shouldReload ?? true)
1088
1089
 
1089
- if (match.cause === 'enter') {
1090
+ if (match.cause === 'enter' || invalidate) {
1090
1091
  match.shouldReloadDeps = shouldReloadDeps
1091
1092
  } else if (match.cause === 'stay') {
1092
1093
  if (typeof shouldReloadDeps === 'object') {
@@ -1192,7 +1193,12 @@ export class Router<
1192
1193
  return matches
1193
1194
  }
1194
1195
 
1195
- load: LoadFn = async () => {
1196
+ invalidate = () =>
1197
+ this.load({
1198
+ invalidate: true,
1199
+ })
1200
+
1201
+ load = async (opts?: { invalidate?: boolean }): Promise<void> => {
1196
1202
  const promise = new Promise<void>(async (resolve, reject) => {
1197
1203
  const next = this.latestLocation
1198
1204
  const prevLocation = this.state.resolvedLocation
@@ -1236,6 +1242,7 @@ export class Router<
1236
1242
  await this.loadMatches({
1237
1243
  matches,
1238
1244
  checkLatest: () => this.checkLatest(promise),
1245
+ invalidate: opts?.invalidate,
1239
1246
  })
1240
1247
  } catch (err) {
1241
1248
  // swallow this error, since we'll display the