@tanstack/react-router 0.0.1-beta.272 → 0.0.1-beta.274

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.
@@ -353,7 +353,7 @@ class Router {
353
353
  context: undefined,
354
354
  abortController: new AbortController(),
355
355
  shouldReloadDeps: undefined,
356
- fetchedAt: 0,
356
+ fetchCount: 0,
357
357
  cause
358
358
  };
359
359
 
@@ -562,10 +562,13 @@ class Router {
562
562
  }) => {
563
563
  let latestPromise;
564
564
  let firstBadMatchIndex;
565
- const updatePendingMatch = match => {
565
+ const updateMatch = match => {
566
+ const isPreload = this.state.preloadMatches.find(d => d.id === match.id);
567
+ const isPending = this.state.pendingMatches?.find(d => d.id === match.id);
568
+ const matchesKey = isPreload ? 'preloadMatches' : isPending ? 'pendingMatches' : 'matches';
566
569
  this.__store.setState(s => ({
567
570
  ...s,
568
- pendingMatches: s.pendingMatches?.map(d => d.id === match.id ? match : d)
571
+ [matchesKey]: s[matchesKey]?.map(d => d.id === match.id ? match : d)
569
572
  }));
570
573
  };
571
574
 
@@ -618,7 +621,7 @@ class Router {
618
621
  from: match.pathname
619
622
  }),
620
623
  buildLocation: this.buildLocation,
621
- cause: match.cause
624
+ cause: preload ? 'preload' : match.cause
622
625
  })) ?? {};
623
626
  if (redirects.isRedirect(beforeLoadContext)) {
624
627
  throw beforeLoadContext;
@@ -648,7 +651,7 @@ class Router {
648
651
  const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
649
652
  const matchPromises = [];
650
653
  validResolvedMatches.forEach((match, index) => {
651
- matchPromises.push((async () => {
654
+ matchPromises.push(new Promise(async resolve => {
652
655
  const parentMatchPromise = matchPromises[index - 1];
653
656
  const route = this.looseRoutesById[match.routeId];
654
657
  const handleErrorAndRedirect = err => {
@@ -663,92 +666,95 @@ class Router {
663
666
  let loadPromise;
664
667
  matches[index] = match = {
665
668
  ...match,
666
- fetchedAt: Date.now(),
667
669
  showPending: false
668
670
  };
671
+ let didShowPending = false;
669
672
  const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
670
- let pendingPromise;
671
- if (!preload && pendingMs && (route.options.pendingComponent ?? this.options.defaultPendingComponent)) {
672
- pendingPromise = new Promise(r => setTimeout(r, pendingMs));
673
- }
674
- if (match.isFetching) {
675
- loadPromise = RouterProvider.getRouteMatch(this.state, match.id)?.loadPromise;
676
- } else {
677
- const loaderContext = {
678
- params: match.params,
679
- search: match.search,
680
- preload: !!preload,
681
- parentMatchPromise,
682
- abortController: match.abortController,
683
- context: match.context,
684
- location: this.state.location,
685
- navigate: opts => this.navigate({
686
- ...opts,
687
- from: match.pathname
688
- }),
689
- cause: match.cause
690
- };
673
+ const pendingMinMs = route.options.pendingMinMs ?? this.options.defaultPendingMinMs;
674
+ const shouldPending = !preload && pendingMs && (route.options.pendingComponent ?? this.options.defaultPendingComponent);
675
+ const fetch = async () => {
676
+ if (match.isFetching) {
677
+ loadPromise = RouterProvider.getRouteMatch(this.state, match.id)?.loadPromise;
678
+ } else {
679
+ const loaderContext = {
680
+ params: match.params,
681
+ search: match.search,
682
+ preload: !!preload,
683
+ parentMatchPromise,
684
+ abortController: match.abortController,
685
+ context: match.context,
686
+ location: this.state.location,
687
+ navigate: opts => this.navigate({
688
+ ...opts,
689
+ from: match.pathname
690
+ }),
691
+ cause: preload ? 'preload' : match.cause
692
+ };
691
693
 
692
- // Default to reloading the route all the time
693
- let shouldReload = true;
694
- let shouldReloadDeps = typeof route.options.shouldReload === 'function' ? route.options.shouldReload?.(loaderContext) : !!(route.options.shouldReload ?? true);
695
- if (match.cause === 'enter' || invalidate) {
696
- match.shouldReloadDeps = shouldReloadDeps;
697
- } else if (match.cause === 'stay') {
698
- if (typeof shouldReloadDeps === 'object') {
699
- // compare the deps to see if they've changed
700
- shouldReload = !utils.deepEqual(shouldReloadDeps, match.shouldReloadDeps);
701
- match.shouldReloadDeps = shouldReloadDeps;
694
+ // Default to reloading the route all the time
695
+ let shouldLoad = true;
696
+ const shouldReloadFn = route.options.shouldReload;
697
+ let shouldReloadDeps = typeof shouldReloadFn === 'function' ? shouldReloadFn(loaderContext) : !!(shouldReloadFn ?? true);
698
+ const compareDeps = () => {
699
+ if (typeof shouldReloadDeps === 'object') {
700
+ // compare the deps to see if they've changed
701
+ shouldLoad = !utils.deepEqual(shouldReloadDeps, match.shouldReloadDeps);
702
+ } else {
703
+ shouldLoad = !!shouldReloadDeps;
704
+ }
705
+ };
706
+
707
+ // If it's the first preload, or the route is entering, or we're
708
+ // invalidating, we definitely need to load the route
709
+ if (invalidate) ; else if (preload) {
710
+ if (!match.fetchCount) ; else {
711
+ compareDeps();
712
+ }
713
+ } else if (match.cause === 'enter') {
714
+ if (!match.fetchCount) ; else {
715
+ compareDeps();
716
+ }
702
717
  } else {
703
- shouldReload = !!shouldReloadDeps;
718
+ compareDeps();
719
+ }
720
+ if (typeof shouldReloadDeps === 'object') {
721
+ matches[index] = match = {
722
+ ...match,
723
+ shouldReloadDeps
724
+ };
704
725
  }
705
- }
706
726
 
707
- // If the user doesn't want the route to reload, just
708
- // resolve with the existing loader data
727
+ // If the user doesn't want the route to reload, just
728
+ // resolve with the existing loader data
709
729
 
710
- if (!shouldReload) {
711
- loadPromise = Promise.resolve(match.loaderData);
712
- } else {
713
- // Otherwise, load the route
714
- matches[index] = match = {
715
- ...match,
716
- isFetching: true
717
- };
718
- const componentsPromise = Promise.all(componentTypes.map(async type => {
719
- const component = route.options[type];
720
- if (component?.preload) {
721
- await component.preload();
730
+ if (!shouldLoad) {
731
+ loadPromise = Promise.resolve(match.loaderData);
732
+ } else {
733
+ if (match.fetchCount && match.status === 'success') {
734
+ resolve();
722
735
  }
723
- }));
724
- const loaderPromise = route.options.loader?.(loaderContext);
725
- loadPromise = Promise.all([componentsPromise, loaderPromise]).then(d => d[1]);
726
- }
727
- }
728
- matches[index] = match = {
729
- ...match,
730
- loadPromise
731
- };
732
- if (!preload) {
733
- updatePendingMatch(match);
734
- }
735
- let didShowPending = false;
736
- const pendingMinMs = route.options.pendingMinMs ?? this.options.defaultPendingMinMs;
737
- await new Promise(async resolve => {
738
- // If the route has a pending component and a pendingMs option,
739
- // forcefully show the pending component
740
- if (pendingPromise) {
741
- pendingPromise.then(() => {
742
- if (latestPromise = checkLatest()) return;
743
- didShowPending = true;
736
+
737
+ // Otherwise, load the route
744
738
  matches[index] = match = {
745
739
  ...match,
746
- showPending: true
740
+ isFetching: true,
741
+ fetchCount: match.fetchCount + 1
747
742
  };
748
- updatePendingMatch(match);
749
- resolve();
750
- });
743
+ const componentsPromise = Promise.all(componentTypes.map(async type => {
744
+ const component = route.options[type];
745
+ if (component?.preload) {
746
+ await component.preload();
747
+ }
748
+ }));
749
+ const loaderPromise = route.options.loader?.(loaderContext);
750
+ loadPromise = Promise.all([componentsPromise, loaderPromise]).then(d => d[1]);
751
+ }
751
752
  }
753
+ matches[index] = match = {
754
+ ...match,
755
+ loadPromise
756
+ };
757
+ updateMatch(match);
752
758
  try {
753
759
  const loaderData = await loadPromise;
754
760
  if (latestPromise = checkLatest()) return await latestPromise;
@@ -789,18 +795,38 @@ class Router {
789
795
  // we already moved the pendingMatches to the matches
790
796
  // state, so we need to update that specific match
791
797
  if (didShowPending && pendingMinMs && match.showPending) {
792
- this.__store.setState(s => ({
793
- ...s,
794
- matches: s.matches?.map(d => d.id === match.id ? match : d)
795
- }));
798
+ updateMatch(match);
796
799
  }
797
800
  }
798
- if (!preload) {
799
- updatePendingMatch(match);
801
+ updateMatch(match);
802
+ };
803
+ if (match.fetchCount && match.status === 'success') {
804
+ // Background Fetching
805
+ fetch();
806
+ } else {
807
+ // Critical Fetching
808
+
809
+ // If we need to potentially show the pending component,
810
+ // start a timer to show it after the pendingMs
811
+ if (shouldPending) {
812
+ new Promise(r => setTimeout(r, pendingMs)).then(async () => {
813
+ if (latestPromise = checkLatest()) return latestPromise;
814
+ didShowPending = true;
815
+ matches[index] = match = {
816
+ ...match,
817
+ showPending: true
818
+ };
819
+ updateMatch(match);
820
+ resolve();
821
+ });
800
822
  }
801
- resolve();
802
- });
803
- })());
823
+ await fetch();
824
+ }
825
+ resolve();
826
+ // No Fetching
827
+
828
+ resolve();
829
+ }));
804
830
  });
805
831
  await Promise.all(matchPromises);
806
832
  return matches;
@@ -823,20 +849,32 @@ class Router {
823
849
  toLocation: next,
824
850
  pathChanged: pathDidChange
825
851
  });
826
-
827
- // Match the routes
828
- let pendingMatches = this.matchRoutes(next.pathname, next.search, {
829
- debug: true
830
- });
852
+ let pendingMatches;
831
853
  const previousMatches = this.state.matches;
854
+ this.__store.batch(() => {
855
+ this.__store.setState(s => ({
856
+ ...s,
857
+ preloadMatches: s.preloadMatches.filter(d => {
858
+ return Date.now() - d.updatedAt < (this.options.defaultPreloadMaxAge ?? 3000);
859
+ })
860
+ }));
832
861
 
833
- // Ingest the new matches
834
- this.__store.setState(s => ({
835
- ...s,
836
- isLoading: true,
837
- location: next,
838
- pendingMatches
839
- }));
862
+ // Match the routes
863
+ pendingMatches = this.matchRoutes(next.pathname, next.search, {
864
+ debug: true
865
+ });
866
+
867
+ // Ingest the new matches
868
+ this.__store.setState(s => ({
869
+ ...s,
870
+ isLoading: true,
871
+ location: next,
872
+ pendingMatches,
873
+ preloadMatches: s.preloadMatches.filter(d => {
874
+ return !pendingMatches.find(e => e.id === d.id);
875
+ })
876
+ }));
877
+ });
840
878
  try {
841
879
  try {
842
880
  // Load the matches
@@ -894,6 +932,17 @@ class Router {
894
932
  let matches = this.matchRoutes(next.pathname, next.search, {
895
933
  throwOnError: true
896
934
  });
935
+ const loadedMatchIds = Object.fromEntries([...this.state.matches, ...(this.state.pendingMatches ?? []), ...this.state.preloadMatches]?.map(d => [d.id, true]));
936
+ this.__store.batch(() => {
937
+ matches.forEach(match => {
938
+ if (!loadedMatchIds[match.id]) {
939
+ this.__store.setState(s => ({
940
+ ...s,
941
+ preloadMatches: [...s.preloadMatches, match]
942
+ }));
943
+ }
944
+ });
945
+ });
897
946
  matches = await this.loadMatches({
898
947
  matches,
899
948
  preload: true,
@@ -956,7 +1005,7 @@ class Router {
956
1005
  dehydrate = () => {
957
1006
  return {
958
1007
  state: {
959
- dehydratedMatches: this.state.matches.map(d => utils.pick(d, ['fetchedAt', 'id', 'status', 'updatedAt', 'loaderData']))
1008
+ dehydratedMatches: this.state.matches.map(d => utils.pick(d, ['id', 'status', 'updatedAt', 'loaderData']))
960
1009
  }
961
1010
  };
962
1011
  };
@@ -1019,6 +1068,7 @@ function getInitialRouterState(location) {
1019
1068
  location,
1020
1069
  matches: [],
1021
1070
  pendingMatches: [],
1071
+ preloadMatches: [],
1022
1072
  lastUpdated: Date.now()
1023
1073
  };
1024
1074
  }