@tanstack/react-router 1.31.0 → 1.31.2

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.
@@ -30,7 +30,6 @@ class Router {
30
30
  )}`;
31
31
  this.resetNextScroll = true;
32
32
  this.shouldViewTransition = void 0;
33
- this.navigateTimeout = null;
34
33
  this.latestLoadPromise = Promise.resolve();
35
34
  this.subscribers = /* @__PURE__ */ new Set();
36
35
  this.injectedHtml = [];
@@ -175,7 +174,9 @@ class Router {
175
174
  });
176
175
  };
177
176
  this.checkLatest = (promise) => {
178
- return this.latestLoadPromise !== promise ? this.latestLoadPromise : void 0;
177
+ if (this.latestLoadPromise !== promise) {
178
+ throw this.latestLoadPromise;
179
+ }
179
180
  };
180
181
  this.parseLocation = (previousLocation) => {
181
182
  const parse = ({
@@ -398,20 +399,20 @@ class Router {
398
399
  };
399
400
  this.buildLocation = (opts) => {
400
401
  const build = (dest = {}, matches) => {
401
- var _a, _b, _c, _d;
402
- const fromPath = dest.from || this.latestLocation.pathname;
403
- let fromSearch = ((_a = dest._fromLocation) == null ? void 0 : _a.search) || this.latestLocation.search;
404
- const fromMatches = this.matchRoutes(fromPath, fromSearch);
402
+ var _a, _b, _c;
403
+ let fromPath = this.latestLocation.pathname;
404
+ let fromSearch = dest.fromSearch || this.latestLocation.search;
405
+ const fromMatches = this.matchRoutes(
406
+ this.latestLocation.pathname,
407
+ fromSearch
408
+ );
409
+ fromPath = ((_a = fromMatches.find((d) => d.id === dest.from)) == null ? void 0 : _a.pathname) || fromPath;
405
410
  fromSearch = ((_b = utils.last(fromMatches)) == null ? void 0 : _b.search) || this.latestLocation.search;
406
411
  const stayingMatches = matches == null ? void 0 : matches.filter(
407
412
  (d) => fromMatches.find((e) => e.routeId === d.routeId)
408
413
  );
409
- const fromRoute = this.looseRoutesById[(_c = utils.last(fromMatches)) == null ? void 0 : _c.routeId];
410
- let pathname = dest.to ? this.resolvePathWithBase(
411
- dest.from ?? this.latestLocation.pathname,
412
- `${dest.to}`
413
- ) : this.resolvePathWithBase(fromRoute == null ? void 0 : fromRoute.fullPath, fromRoute == null ? void 0 : fromRoute.fullPath);
414
- const prevParams = { ...(_d = utils.last(fromMatches)) == null ? void 0 : _d.params };
414
+ let pathname = dest.to ? this.resolvePathWithBase(fromPath, `${dest.to}`) : this.resolvePathWithBase(fromPath, fromPath);
415
+ const prevParams = { ...(_c = utils.last(fromMatches)) == null ? void 0 : _c.params };
415
416
  let nextParams = (dest.params ?? true) === true ? prevParams : { ...prevParams, ...utils.functionalUpdate(dest.params, prevParams) };
416
417
  if (Object.keys(nextParams).length > 0) {
417
418
  matches == null ? void 0 : matches.map((d) => this.looseRoutesById[d.routeId].options.stringifyParams).filter(Boolean).forEach((fn) => {
@@ -505,8 +506,6 @@ class Router {
505
506
  viewTransition,
506
507
  ...next
507
508
  }) => {
508
- if (this.navigateTimeout)
509
- clearTimeout(this.navigateTimeout);
510
509
  const isSameUrl = this.latestLocation.href === next.href;
511
510
  if (!isSameUrl) {
512
511
  let { maskedLocation, ...nextHistory } = next;
@@ -576,14 +575,131 @@ class Router {
576
575
  // to: toString,
577
576
  });
578
577
  };
578
+ this.load = async () => {
579
+ const promise = utils.createControlledPromise();
580
+ this.latestLoadPromise = promise;
581
+ let redirect;
582
+ let notFound$1;
583
+ this.startReactTransition(async () => {
584
+ try {
585
+ const next = this.latestLocation;
586
+ const prevLocation = this.state.resolvedLocation;
587
+ const pathDidChange = prevLocation.href !== next.href;
588
+ this.cancelMatches();
589
+ this.emit({
590
+ type: "onBeforeLoad",
591
+ fromLocation: prevLocation,
592
+ toLocation: next,
593
+ pathChanged: pathDidChange
594
+ });
595
+ let pendingMatches;
596
+ this.__store.batch(() => {
597
+ this.cleanCache();
598
+ pendingMatches = this.matchRoutes(next.pathname, next.search);
599
+ this.__store.setState((s) => ({
600
+ ...s,
601
+ status: "pending",
602
+ isLoading: true,
603
+ location: next,
604
+ pendingMatches,
605
+ // If a cached moved to pendingMatches, remove it from cachedMatches
606
+ cachedMatches: s.cachedMatches.filter((d) => {
607
+ return !pendingMatches.find((e) => e.id === d.id);
608
+ })
609
+ }));
610
+ });
611
+ await this.loadMatches({
612
+ matches: pendingMatches,
613
+ location: next,
614
+ checkLatest: () => this.checkLatest(promise),
615
+ onReady: async () => {
616
+ await this.startViewTransition(async () => {
617
+ let exitingMatches;
618
+ let enteringMatches;
619
+ let stayingMatches;
620
+ this.__store.batch(() => {
621
+ this.__store.setState((s) => {
622
+ const previousMatches = s.matches;
623
+ const newMatches = s.pendingMatches || s.matches;
624
+ exitingMatches = previousMatches.filter(
625
+ (match) => !newMatches.find((d) => d.id === match.id)
626
+ );
627
+ enteringMatches = newMatches.filter(
628
+ (match) => !previousMatches.find((d) => d.id === match.id)
629
+ );
630
+ stayingMatches = previousMatches.filter(
631
+ (match) => newMatches.find((d) => d.id === match.id)
632
+ );
633
+ return {
634
+ ...s,
635
+ isLoading: false,
636
+ matches: newMatches,
637
+ pendingMatches: void 0,
638
+ cachedMatches: [
639
+ ...s.cachedMatches,
640
+ ...exitingMatches.filter((d) => d.status !== "error")
641
+ ]
642
+ };
643
+ });
644
+ this.cleanCache();
645
+ });
646
+ [
647
+ [exitingMatches, "onLeave"],
648
+ [enteringMatches, "onEnter"],
649
+ [stayingMatches, "onStay"]
650
+ ].forEach(([matches, hook]) => {
651
+ matches.forEach((match) => {
652
+ var _a, _b;
653
+ (_b = (_a = this.looseRoutesById[match.routeId].options)[hook]) == null ? void 0 : _b.call(_a, match);
654
+ });
655
+ });
656
+ });
657
+ }
658
+ });
659
+ } catch (err) {
660
+ if (redirects.isResolvedRedirect(err)) {
661
+ redirect = err;
662
+ if (!this.isServer) {
663
+ this.navigate({ ...err, replace: true });
664
+ this.load();
665
+ }
666
+ } else if (notFound.isNotFound(err)) {
667
+ notFound$1 = err;
668
+ }
669
+ this.__store.setState((s) => ({
670
+ ...s,
671
+ statusCode: (redirect == null ? void 0 : redirect.statusCode) || notFound$1 ? 404 : s.matches.some((d) => d.status === "error") ? 500 : 200,
672
+ redirect
673
+ }));
674
+ }
675
+ promise.resolve();
676
+ });
677
+ return this.latestLoadPromise;
678
+ };
679
+ this.startViewTransition = async (fn) => {
680
+ var _a, _b;
681
+ const shouldViewTransition = this.shouldViewTransition ?? this.options.defaultViewTransition;
682
+ delete this.shouldViewTransition;
683
+ ((_b = (_a = shouldViewTransition && typeof document !== "undefined" ? document : void 0) == null ? void 0 : _a.startViewTransition) == null ? void 0 : _b.call(_a, fn)) || fn();
684
+ };
579
685
  this.loadMatches = async ({
580
686
  checkLatest,
581
687
  location,
582
688
  matches,
583
- preload
689
+ preload,
690
+ onReady
584
691
  }) => {
585
- let latestPromise;
586
692
  let firstBadMatchIndex;
693
+ let rendered = false;
694
+ const triggerOnReady = async () => {
695
+ if (!rendered) {
696
+ rendered = true;
697
+ await (onReady == null ? void 0 : onReady());
698
+ }
699
+ };
700
+ if (!this.isServer && !this.state.matches.length) {
701
+ triggerOnReady();
702
+ }
587
703
  const updateMatch = (id, updater, opts) => {
588
704
  var _a;
589
705
  let updated;
@@ -601,41 +717,64 @@ class Router {
601
717
  });
602
718
  return updated;
603
719
  };
720
+ const handleRedirectAndNotFound = (match, err) => {
721
+ if (redirects.isResolvedRedirect(err))
722
+ throw err;
723
+ if (redirects.isRedirect(err) || notFound.isNotFound(err)) {
724
+ updateMatch(match.id, (prev) => ({
725
+ ...prev,
726
+ status: redirects.isRedirect(err) ? "redirected" : notFound.isNotFound(err) ? "notFound" : "error",
727
+ isFetching: false,
728
+ error: err
729
+ }));
730
+ rendered = true;
731
+ if (!err.routeId) {
732
+ err.routeId = match.routeId;
733
+ }
734
+ if (redirects.isRedirect(err)) {
735
+ err = this.resolveRedirect(err);
736
+ throw err;
737
+ } else if (notFound.isNotFound(err)) {
738
+ this.handleNotFound(matches, err);
739
+ throw err;
740
+ }
741
+ }
742
+ };
604
743
  try {
605
744
  await new Promise((resolveAll, rejectAll) => {
606
745
  ;
607
746
  (async () => {
608
747
  var _a, _b;
609
748
  try {
610
- const handleRedirectAndNotFound = (match, err) => {
611
- if (redirects.isRedirect(err) || notFound.isNotFound(err)) {
612
- updateMatch(match.id, (prev) => ({
613
- ...prev,
614
- status: redirects.isRedirect(err) ? "redirected" : notFound.isNotFound(err) ? "notFound" : "error",
615
- isFetching: false,
616
- error: err
617
- }));
618
- if (!err.routeId) {
619
- ;
620
- err.routeId = match.routeId;
621
- }
622
- if (redirects.isRedirect(err)) {
623
- const redirect = this.resolveRedirect(err);
624
- if (!preload && !this.isServer) {
625
- this.navigate({ ...redirect, replace: true });
626
- }
627
- throw redirect;
628
- } else if (notFound.isNotFound(err)) {
629
- if (!preload)
630
- this.handleNotFound(matches, err);
631
- throw err;
632
- }
633
- }
634
- };
635
749
  for (let [index, match] of matches.entries()) {
636
750
  const parentMatch = matches[index - 1];
637
751
  const route2 = this.looseRoutesById[match.routeId];
638
752
  const abortController = new AbortController();
753
+ let loadPromise = match.loadPromise;
754
+ const pendingMs = route2.options.pendingMs ?? this.options.defaultPendingMs;
755
+ const shouldPending = !!(onReady && !this.isServer && !preload && (route2.options.loader || route2.options.beforeLoad) && typeof pendingMs === "number" && pendingMs !== Infinity && (route2.options.pendingComponent ?? this.options.defaultPendingComponent));
756
+ if (shouldPending) {
757
+ setTimeout(() => {
758
+ try {
759
+ checkLatest();
760
+ triggerOnReady();
761
+ } catch {
762
+ }
763
+ }, pendingMs);
764
+ }
765
+ if (match.isFetching) {
766
+ continue;
767
+ }
768
+ const previousResolve = loadPromise.resolve;
769
+ loadPromise = utils.createControlledPromise(
770
+ // Resolve the old when we we resolve the new one
771
+ previousResolve
772
+ );
773
+ matches[index] = match = updateMatch(match.id, (prev) => ({
774
+ ...prev,
775
+ isFetching: "beforeLoad",
776
+ loadPromise
777
+ }));
639
778
  const handleSerialError = (err, routerCode) => {
640
779
  var _a2, _b2;
641
780
  err.routerCode = routerCode;
@@ -663,19 +802,6 @@ class Router {
663
802
  }
664
803
  try {
665
804
  const parentContext = (parentMatch == null ? void 0 : parentMatch.context) ?? this.options.context ?? {};
666
- const pendingMs = route2.options.pendingMs ?? this.options.defaultPendingMs;
667
- const pendingPromise = typeof pendingMs !== "number" || pendingMs <= 0 ? Promise.resolve() : new Promise((r) => {
668
- if (pendingMs !== Infinity)
669
- setTimeout(r, pendingMs);
670
- });
671
- const shouldPending = !this.isServer && !preload && (route2.options.loader || route2.options.beforeLoad) && typeof pendingMs === "number" && (route2.options.pendingComponent ?? this.options.defaultPendingComponent);
672
- if (shouldPending) {
673
- pendingPromise.then(async () => {
674
- if (latestPromise = checkLatest())
675
- return latestPromise;
676
- resolveAll();
677
- });
678
- }
679
805
  const beforeLoadContext = await ((_b = (_a = route2.options).beforeLoad) == null ? void 0 : _b.call(_a, {
680
806
  search: match.search,
681
807
  abortController,
@@ -687,6 +813,7 @@ class Router {
687
813
  buildLocation: this.buildLocation,
688
814
  cause: preload ? "preload" : match.cause
689
815
  })) ?? {};
816
+ checkLatest();
690
817
  if (redirects.isRedirect(beforeLoadContext) || notFound.isNotFound(beforeLoadContext)) {
691
818
  handleSerialError(beforeLoadContext, "BEFORE_LOAD");
692
819
  }
@@ -710,6 +837,7 @@ class Router {
710
837
  updateMatch(match.id, () => match);
711
838
  }
712
839
  }
840
+ checkLatest();
713
841
  const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
714
842
  const matchPromises = [];
715
843
  await Promise.all(
@@ -734,13 +862,11 @@ class Router {
734
862
  let lazyPromise = Promise.resolve();
735
863
  let componentsPromise = Promise.resolve();
736
864
  let loaderPromise = existing.loaderPromise;
737
- let loadPromise = existing.loadPromise;
738
865
  const potentialPendingMinPromise = async () => {
739
866
  const latestMatch = RouterProvider.getRouteMatch(this.state, match.id);
740
867
  if (latestMatch == null ? void 0 : latestMatch.minPendingPromise) {
741
868
  await latestMatch.minPendingPromise;
742
- if (latestPromise = checkLatest())
743
- return await latestPromise;
869
+ checkLatest();
744
870
  updateMatch(latestMatch.id, (prev) => ({
745
871
  ...prev,
746
872
  minPendingPromise: void 0
@@ -748,12 +874,15 @@ class Router {
748
874
  }
749
875
  };
750
876
  try {
751
- if (!match.isFetching) {
752
- matches[index] = match = {
753
- ...match,
754
- isFetching: true,
755
- fetchCount: match.fetchCount + 1
756
- };
877
+ if (match.isFetching === "beforeLoad") {
878
+ matches[index] = match = updateMatch(
879
+ match.id,
880
+ (prev) => ({
881
+ ...prev,
882
+ isFetching: "loader",
883
+ fetchCount: match.fetchCount + 1
884
+ })
885
+ );
757
886
  lazyPromise = ((_a2 = route2.lazyFn) == null ? void 0 : _a2.call(route2).then((lazyRoute) => {
758
887
  Object.assign(route2.options, lazyRoute.options);
759
888
  })) || Promise.resolve();
@@ -768,29 +897,21 @@ class Router {
768
897
  )
769
898
  );
770
899
  await lazyPromise;
771
- if (latestPromise = checkLatest())
772
- return await latestPromise;
900
+ checkLatest();
773
901
  loaderPromise = (_c = (_b2 = route2.options).loader) == null ? void 0 : _c.call(_b2, loaderContext);
774
- const previousResolve = loadPromise.resolve;
775
- loadPromise = utils.createControlledPromise(
776
- // Resolve the old when we we resolve the new one
777
- previousResolve
902
+ matches[index] = match = updateMatch(
903
+ match.id,
904
+ (prev) => ({
905
+ ...prev,
906
+ loaderPromise
907
+ })
778
908
  );
779
909
  }
780
- matches[index] = match = updateMatch(match.id, (prev) => ({
781
- ...prev,
782
- loaderPromise,
783
- loadPromise
784
- }));
785
910
  const loaderData = await loaderPromise;
786
- if (latestPromise = checkLatest())
787
- return await latestPromise;
911
+ checkLatest();
788
912
  handleRedirectAndNotFound(match, loaderData);
789
- if (latestPromise = checkLatest())
790
- return await latestPromise;
791
913
  await potentialPendingMinPromise();
792
- if (latestPromise = checkLatest())
793
- return await latestPromise;
914
+ checkLatest();
794
915
  const meta = (_e = (_d = route2.options).meta) == null ? void 0 : _e.call(_d, {
795
916
  params: match.params,
796
917
  loaderData
@@ -809,12 +930,10 @@ class Router {
809
930
  headers
810
931
  }));
811
932
  } catch (e) {
933
+ checkLatest();
812
934
  let error = e;
813
- if (latestPromise = checkLatest())
814
- return await latestPromise;
815
935
  await potentialPendingMinPromise();
816
- if (latestPromise = checkLatest())
817
- return await latestPromise;
936
+ checkLatest();
818
937
  handleRedirectAndNotFound(match, e);
819
938
  try {
820
939
  (_i = (_h = route2.options).onError) == null ? void 0 : _i.call(_h, e);
@@ -830,9 +949,8 @@ class Router {
830
949
  }));
831
950
  }
832
951
  await componentsPromise;
833
- if (latestPromise = checkLatest())
834
- return await latestPromise;
835
- loadPromise.resolve();
952
+ checkLatest();
953
+ match.loadPromise.resolve();
836
954
  };
837
955
  const age = Date.now() - match.updatedAt;
838
956
  const staleAge = preload ? route2.options.preloadStaleTime ?? this.options.defaultPreloadStaleTime ?? 3e4 : route2.options.staleTime ?? this.options.defaultStaleTime ?? 0;
@@ -846,8 +964,7 @@ class Router {
846
964
  try {
847
965
  await fetch();
848
966
  } catch (err) {
849
- if (latestPromise = checkLatest())
850
- return await latestPromise;
967
+ checkLatest();
851
968
  handleRedirectAndNotFound(match, err);
852
969
  }
853
970
  };
@@ -860,14 +977,14 @@ class Router {
860
977
  }
861
978
  })
862
979
  );
863
- if (latestPromise = checkLatest())
864
- return await latestPromise;
980
+ checkLatest();
865
981
  resolveAll();
866
982
  } catch (err) {
867
983
  rejectAll(err);
868
984
  }
869
985
  })();
870
986
  });
987
+ await triggerOnReady();
871
988
  } catch (err) {
872
989
  if (redirects.isRedirect(err) || notFound.isNotFound(err)) {
873
990
  throw err;
@@ -892,115 +1009,6 @@ class Router {
892
1009
  });
893
1010
  return this.load();
894
1011
  };
895
- this.load = async () => {
896
- let resolveLoad;
897
- let rejectLoad;
898
- const promise = new Promise((resolve, reject) => {
899
- resolveLoad = resolve;
900
- rejectLoad = reject;
901
- });
902
- this.latestLoadPromise = promise;
903
- let latestPromise;
904
- this.startReactTransition(async () => {
905
- var _a, _b;
906
- try {
907
- const next = this.latestLocation;
908
- const prevLocation = this.state.resolvedLocation;
909
- const pathDidChange = prevLocation.href !== next.href;
910
- this.cancelMatches();
911
- this.emit({
912
- type: "onBeforeLoad",
913
- fromLocation: prevLocation,
914
- toLocation: next,
915
- pathChanged: pathDidChange
916
- });
917
- let pendingMatches;
918
- const previousMatches = this.state.matches;
919
- this.__store.batch(() => {
920
- this.cleanCache();
921
- pendingMatches = this.matchRoutes(next.pathname, next.search);
922
- this.__store.setState((s) => ({
923
- ...s,
924
- status: "pending",
925
- isLoading: true,
926
- location: next,
927
- pendingMatches,
928
- cachedMatches: s.cachedMatches.filter((d) => {
929
- return !pendingMatches.find((e) => e.id === d.id);
930
- })
931
- }));
932
- });
933
- let redirect;
934
- let notFound$1;
935
- try {
936
- const loadMatchesPromise = this.loadMatches({
937
- matches: pendingMatches,
938
- location: next,
939
- checkLatest: () => this.checkLatest(promise)
940
- });
941
- if (previousMatches.length || this.isServer) {
942
- await loadMatchesPromise;
943
- }
944
- } catch (err) {
945
- if (redirects.isRedirect(err)) {
946
- redirect = err;
947
- } else if (notFound.isNotFound(err)) {
948
- notFound$1 = err;
949
- }
950
- }
951
- if (latestPromise = this.checkLatest(promise)) {
952
- return latestPromise;
953
- }
954
- const exitingMatches = previousMatches.filter(
955
- (match) => !pendingMatches.find((d) => d.id === match.id)
956
- );
957
- const enteringMatches = pendingMatches.filter(
958
- (match) => !previousMatches.find((d) => d.id === match.id)
959
- );
960
- const stayingMatches = previousMatches.filter(
961
- (match) => pendingMatches.find((d) => d.id === match.id)
962
- );
963
- const shouldViewTransition = this.shouldViewTransition ?? this.options.defaultViewTransition;
964
- delete this.shouldViewTransition;
965
- const apply = () => {
966
- this.__store.batch(() => {
967
- this.__store.setState((s) => ({
968
- ...s,
969
- isLoading: false,
970
- matches: s.pendingMatches,
971
- pendingMatches: void 0,
972
- cachedMatches: [
973
- ...s.cachedMatches,
974
- ...exitingMatches.filter((d) => d.status !== "error")
975
- ],
976
- statusCode: (redirect == null ? void 0 : redirect.statusCode) || notFound$1 ? 404 : s.matches.some((d) => d.status === "error") ? 500 : 200,
977
- redirect
978
- }));
979
- this.cleanCache();
980
- });
981
- [
982
- [exitingMatches, "onLeave"],
983
- [enteringMatches, "onEnter"],
984
- [stayingMatches, "onStay"]
985
- ].forEach(([matches, hook]) => {
986
- matches.forEach((match) => {
987
- var _a2, _b2;
988
- (_b2 = (_a2 = this.looseRoutesById[match.routeId].options)[hook]) == null ? void 0 : _b2.call(_a2, match);
989
- });
990
- });
991
- resolveLoad();
992
- };
993
- ((_b = (_a = shouldViewTransition && typeof document !== "undefined" ? document : void 0) == null ? void 0 : _a.startViewTransition) == null ? void 0 : _b.call(_a, apply)) || apply();
994
- } catch (err) {
995
- if (latestPromise = this.checkLatest(promise)) {
996
- return latestPromise;
997
- }
998
- console.error("Load Error", err);
999
- rejectLoad(err);
1000
- }
1001
- });
1002
- return this.latestLoadPromise;
1003
- };
1004
1012
  this.resolveRedirect = (err) => {
1005
1013
  const redirect = err;
1006
1014
  if (!redirect.href) {
@@ -1063,7 +1071,7 @@ class Router {
1063
1071
  } catch (err) {
1064
1072
  if (redirects.isRedirect(err)) {
1065
1073
  return await this.preloadRoute({
1066
- _fromDest: next,
1074
+ fromSearch: next.search,
1067
1075
  from: next.pathname,
1068
1076
  ...err
1069
1077
  });