@tanstack/react-router 1.31.1 → 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.
@@ -7,7 +7,7 @@ import { defaultStringifySearch, defaultParseSearch } from "./searchParams.js";
7
7
  import { createControlledPromise, replaceEqualDeep, pick, last, deepEqual, escapeJSON, functionalUpdate } from "./utils.js";
8
8
  import { getRouteMatch } from "./RouterProvider.js";
9
9
  import { trimPath, trimPathLeft, parsePathname, resolvePath, cleanPath, matchPathname, trimPathRight, interpolatePath, joinPaths } from "./path.js";
10
- import { isRedirect } from "./redirects.js";
10
+ import { isResolvedRedirect, isRedirect } from "./redirects.js";
11
11
  import { isNotFound } from "./not-found.js";
12
12
  const componentTypes = [
13
13
  "component",
@@ -172,7 +172,9 @@ class Router {
172
172
  });
173
173
  };
174
174
  this.checkLatest = (promise) => {
175
- return this.latestLoadPromise !== promise ? this.latestLoadPromise : void 0;
175
+ if (this.latestLoadPromise !== promise) {
176
+ throw this.latestLoadPromise;
177
+ }
176
178
  };
177
179
  this.parseLocation = (previousLocation) => {
178
180
  const parse = ({
@@ -571,14 +573,131 @@ class Router {
571
573
  // to: toString,
572
574
  });
573
575
  };
576
+ this.load = async () => {
577
+ const promise = createControlledPromise();
578
+ this.latestLoadPromise = promise;
579
+ let redirect;
580
+ let notFound;
581
+ this.startReactTransition(async () => {
582
+ try {
583
+ const next = this.latestLocation;
584
+ const prevLocation = this.state.resolvedLocation;
585
+ const pathDidChange = prevLocation.href !== next.href;
586
+ this.cancelMatches();
587
+ this.emit({
588
+ type: "onBeforeLoad",
589
+ fromLocation: prevLocation,
590
+ toLocation: next,
591
+ pathChanged: pathDidChange
592
+ });
593
+ let pendingMatches;
594
+ this.__store.batch(() => {
595
+ this.cleanCache();
596
+ pendingMatches = this.matchRoutes(next.pathname, next.search);
597
+ this.__store.setState((s) => ({
598
+ ...s,
599
+ status: "pending",
600
+ isLoading: true,
601
+ location: next,
602
+ pendingMatches,
603
+ // If a cached moved to pendingMatches, remove it from cachedMatches
604
+ cachedMatches: s.cachedMatches.filter((d) => {
605
+ return !pendingMatches.find((e) => e.id === d.id);
606
+ })
607
+ }));
608
+ });
609
+ await this.loadMatches({
610
+ matches: pendingMatches,
611
+ location: next,
612
+ checkLatest: () => this.checkLatest(promise),
613
+ onReady: async () => {
614
+ await this.startViewTransition(async () => {
615
+ let exitingMatches;
616
+ let enteringMatches;
617
+ let stayingMatches;
618
+ this.__store.batch(() => {
619
+ this.__store.setState((s) => {
620
+ const previousMatches = s.matches;
621
+ const newMatches = s.pendingMatches || s.matches;
622
+ exitingMatches = previousMatches.filter(
623
+ (match) => !newMatches.find((d) => d.id === match.id)
624
+ );
625
+ enteringMatches = newMatches.filter(
626
+ (match) => !previousMatches.find((d) => d.id === match.id)
627
+ );
628
+ stayingMatches = previousMatches.filter(
629
+ (match) => newMatches.find((d) => d.id === match.id)
630
+ );
631
+ return {
632
+ ...s,
633
+ isLoading: false,
634
+ matches: newMatches,
635
+ pendingMatches: void 0,
636
+ cachedMatches: [
637
+ ...s.cachedMatches,
638
+ ...exitingMatches.filter((d) => d.status !== "error")
639
+ ]
640
+ };
641
+ });
642
+ this.cleanCache();
643
+ });
644
+ [
645
+ [exitingMatches, "onLeave"],
646
+ [enteringMatches, "onEnter"],
647
+ [stayingMatches, "onStay"]
648
+ ].forEach(([matches, hook]) => {
649
+ matches.forEach((match) => {
650
+ var _a, _b;
651
+ (_b = (_a = this.looseRoutesById[match.routeId].options)[hook]) == null ? void 0 : _b.call(_a, match);
652
+ });
653
+ });
654
+ });
655
+ }
656
+ });
657
+ } catch (err) {
658
+ if (isResolvedRedirect(err)) {
659
+ redirect = err;
660
+ if (!this.isServer) {
661
+ this.navigate({ ...err, replace: true });
662
+ this.load();
663
+ }
664
+ } else if (isNotFound(err)) {
665
+ notFound = err;
666
+ }
667
+ this.__store.setState((s) => ({
668
+ ...s,
669
+ statusCode: (redirect == null ? void 0 : redirect.statusCode) || notFound ? 404 : s.matches.some((d) => d.status === "error") ? 500 : 200,
670
+ redirect
671
+ }));
672
+ }
673
+ promise.resolve();
674
+ });
675
+ return this.latestLoadPromise;
676
+ };
677
+ this.startViewTransition = async (fn) => {
678
+ var _a, _b;
679
+ const shouldViewTransition = this.shouldViewTransition ?? this.options.defaultViewTransition;
680
+ delete this.shouldViewTransition;
681
+ ((_b = (_a = shouldViewTransition && typeof document !== "undefined" ? document : void 0) == null ? void 0 : _a.startViewTransition) == null ? void 0 : _b.call(_a, fn)) || fn();
682
+ };
574
683
  this.loadMatches = async ({
575
684
  checkLatest,
576
685
  location,
577
686
  matches,
578
- preload
687
+ preload,
688
+ onReady
579
689
  }) => {
580
- let latestPromise;
581
690
  let firstBadMatchIndex;
691
+ let rendered = false;
692
+ const triggerOnReady = async () => {
693
+ if (!rendered) {
694
+ rendered = true;
695
+ await (onReady == null ? void 0 : onReady());
696
+ }
697
+ };
698
+ if (!this.isServer && !this.state.matches.length) {
699
+ triggerOnReady();
700
+ }
582
701
  const updateMatch = (id, updater, opts) => {
583
702
  var _a;
584
703
  let updated;
@@ -596,38 +715,51 @@ class Router {
596
715
  });
597
716
  return updated;
598
717
  };
718
+ const handleRedirectAndNotFound = (match, err) => {
719
+ if (isResolvedRedirect(err))
720
+ throw err;
721
+ if (isRedirect(err) || isNotFound(err)) {
722
+ updateMatch(match.id, (prev) => ({
723
+ ...prev,
724
+ status: isRedirect(err) ? "redirected" : isNotFound(err) ? "notFound" : "error",
725
+ isFetching: false,
726
+ error: err
727
+ }));
728
+ rendered = true;
729
+ if (!err.routeId) {
730
+ err.routeId = match.routeId;
731
+ }
732
+ if (isRedirect(err)) {
733
+ err = this.resolveRedirect(err);
734
+ throw err;
735
+ } else if (isNotFound(err)) {
736
+ this.handleNotFound(matches, err);
737
+ throw err;
738
+ }
739
+ }
740
+ };
599
741
  try {
600
742
  await new Promise((resolveAll, rejectAll) => {
601
743
  ;
602
744
  (async () => {
603
745
  var _a, _b;
604
746
  try {
605
- const handleRedirectAndNotFound = (match, err) => {
606
- if (isRedirect(err) || isNotFound(err)) {
607
- updateMatch(match.id, (prev) => ({
608
- ...prev,
609
- status: isRedirect(err) ? "redirected" : isNotFound(err) ? "notFound" : "error",
610
- isFetching: false,
611
- error: err
612
- }));
613
- if (!err.routeId) {
614
- ;
615
- err.routeId = match.routeId;
616
- }
617
- if (isRedirect(err)) {
618
- err = this.resolveRedirect(err);
619
- throw err;
620
- } else if (isNotFound(err)) {
621
- this.handleNotFound(matches, err);
622
- throw err;
623
- }
624
- }
625
- };
626
747
  for (let [index, match] of matches.entries()) {
627
748
  const parentMatch = matches[index - 1];
628
749
  const route = this.looseRoutesById[match.routeId];
629
750
  const abortController = new AbortController();
630
751
  let loadPromise = match.loadPromise;
752
+ const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
753
+ const shouldPending = !!(onReady && !this.isServer && !preload && (route.options.loader || route.options.beforeLoad) && typeof pendingMs === "number" && pendingMs !== Infinity && (route.options.pendingComponent ?? this.options.defaultPendingComponent));
754
+ if (shouldPending) {
755
+ setTimeout(() => {
756
+ try {
757
+ checkLatest();
758
+ triggerOnReady();
759
+ } catch {
760
+ }
761
+ }, pendingMs);
762
+ }
631
763
  if (match.isFetching) {
632
764
  continue;
633
765
  }
@@ -668,19 +800,6 @@ class Router {
668
800
  }
669
801
  try {
670
802
  const parentContext = (parentMatch == null ? void 0 : parentMatch.context) ?? this.options.context ?? {};
671
- const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
672
- const pendingPromise = typeof pendingMs !== "number" || pendingMs <= 0 ? Promise.resolve() : new Promise((r) => {
673
- if (pendingMs !== Infinity)
674
- setTimeout(r, pendingMs);
675
- });
676
- const shouldPending = !this.isServer && !preload && (route.options.loader || route.options.beforeLoad) && typeof pendingMs === "number" && (route.options.pendingComponent ?? this.options.defaultPendingComponent);
677
- if (shouldPending) {
678
- pendingPromise.then(async () => {
679
- if (latestPromise = checkLatest())
680
- return latestPromise;
681
- resolveAll();
682
- });
683
- }
684
803
  const beforeLoadContext = await ((_b = (_a = route.options).beforeLoad) == null ? void 0 : _b.call(_a, {
685
804
  search: match.search,
686
805
  abortController,
@@ -692,8 +811,7 @@ class Router {
692
811
  buildLocation: this.buildLocation,
693
812
  cause: preload ? "preload" : match.cause
694
813
  })) ?? {};
695
- if (latestPromise = checkLatest())
696
- return latestPromise;
814
+ checkLatest();
697
815
  if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
698
816
  handleSerialError(beforeLoadContext, "BEFORE_LOAD");
699
817
  }
@@ -717,8 +835,7 @@ class Router {
717
835
  updateMatch(match.id, () => match);
718
836
  }
719
837
  }
720
- if (latestPromise = checkLatest())
721
- return latestPromise;
838
+ checkLatest();
722
839
  const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
723
840
  const matchPromises = [];
724
841
  await Promise.all(
@@ -747,8 +864,7 @@ class Router {
747
864
  const latestMatch = getRouteMatch(this.state, match.id);
748
865
  if (latestMatch == null ? void 0 : latestMatch.minPendingPromise) {
749
866
  await latestMatch.minPendingPromise;
750
- if (latestPromise = checkLatest())
751
- return await latestPromise;
867
+ checkLatest();
752
868
  updateMatch(latestMatch.id, (prev) => ({
753
869
  ...prev,
754
870
  minPendingPromise: void 0
@@ -779,8 +895,7 @@ class Router {
779
895
  )
780
896
  );
781
897
  await lazyPromise;
782
- if (latestPromise = checkLatest())
783
- return await latestPromise;
898
+ checkLatest();
784
899
  loaderPromise = (_c = (_b2 = route.options).loader) == null ? void 0 : _c.call(_b2, loaderContext);
785
900
  matches[index] = match = updateMatch(
786
901
  match.id,
@@ -791,14 +906,10 @@ class Router {
791
906
  );
792
907
  }
793
908
  const loaderData = await loaderPromise;
794
- if (latestPromise = checkLatest())
795
- return await latestPromise;
909
+ checkLatest();
796
910
  handleRedirectAndNotFound(match, loaderData);
797
- if (latestPromise = checkLatest())
798
- return await latestPromise;
799
911
  await potentialPendingMinPromise();
800
- if (latestPromise = checkLatest())
801
- return await latestPromise;
912
+ checkLatest();
802
913
  const meta = (_e = (_d = route.options).meta) == null ? void 0 : _e.call(_d, {
803
914
  params: match.params,
804
915
  loaderData
@@ -817,12 +928,10 @@ class Router {
817
928
  headers
818
929
  }));
819
930
  } catch (e) {
931
+ checkLatest();
820
932
  let error = e;
821
- if (latestPromise = checkLatest())
822
- return await latestPromise;
823
933
  await potentialPendingMinPromise();
824
- if (latestPromise = checkLatest())
825
- return await latestPromise;
934
+ checkLatest();
826
935
  handleRedirectAndNotFound(match, e);
827
936
  try {
828
937
  (_i = (_h = route.options).onError) == null ? void 0 : _i.call(_h, e);
@@ -838,8 +947,7 @@ class Router {
838
947
  }));
839
948
  }
840
949
  await componentsPromise;
841
- if (latestPromise = checkLatest())
842
- return await latestPromise;
950
+ checkLatest();
843
951
  match.loadPromise.resolve();
844
952
  };
845
953
  const age = Date.now() - match.updatedAt;
@@ -854,8 +962,7 @@ class Router {
854
962
  try {
855
963
  await fetch();
856
964
  } catch (err) {
857
- if (latestPromise = checkLatest())
858
- return await latestPromise;
965
+ checkLatest();
859
966
  handleRedirectAndNotFound(match, err);
860
967
  }
861
968
  };
@@ -868,14 +975,14 @@ class Router {
868
975
  }
869
976
  })
870
977
  );
871
- if (latestPromise = checkLatest())
872
- return await latestPromise;
978
+ checkLatest();
873
979
  resolveAll();
874
980
  } catch (err) {
875
981
  rejectAll(err);
876
982
  }
877
983
  })();
878
984
  });
985
+ await triggerOnReady();
879
986
  } catch (err) {
880
987
  if (isRedirect(err) || isNotFound(err)) {
881
988
  throw err;
@@ -900,122 +1007,6 @@ class Router {
900
1007
  });
901
1008
  return this.load();
902
1009
  };
903
- this.load = async () => {
904
- let resolveLoad;
905
- let rejectLoad;
906
- const promise = new Promise((resolve, reject) => {
907
- resolveLoad = resolve;
908
- rejectLoad = reject;
909
- });
910
- this.latestLoadPromise = promise;
911
- let latestPromise;
912
- this.startReactTransition(async () => {
913
- var _a, _b;
914
- try {
915
- const next = this.latestLocation;
916
- const prevLocation = this.state.resolvedLocation;
917
- const pathDidChange = prevLocation.href !== next.href;
918
- this.cancelMatches();
919
- this.emit({
920
- type: "onBeforeLoad",
921
- fromLocation: prevLocation,
922
- toLocation: next,
923
- pathChanged: pathDidChange
924
- });
925
- let pendingMatches;
926
- const previousMatches = this.state.matches;
927
- this.__store.batch(() => {
928
- this.cleanCache();
929
- pendingMatches = this.matchRoutes(next.pathname, next.search);
930
- this.__store.setState((s) => ({
931
- ...s,
932
- status: "pending",
933
- isLoading: true,
934
- location: next,
935
- pendingMatches,
936
- cachedMatches: s.cachedMatches.filter((d) => {
937
- return !pendingMatches.find((e) => e.id === d.id);
938
- })
939
- }));
940
- });
941
- let redirect;
942
- let notFound;
943
- const loadMatches = () => this.loadMatches({
944
- matches: pendingMatches,
945
- location: next,
946
- checkLatest: () => this.checkLatest(promise)
947
- });
948
- if (previousMatches.length || this.isServer) {
949
- try {
950
- await loadMatches();
951
- } catch (err) {
952
- if (isRedirect(err)) {
953
- redirect = err;
954
- } else if (isNotFound(err)) {
955
- notFound = err;
956
- }
957
- }
958
- } else {
959
- loadMatches().catch((err) => {
960
- if (isRedirect(err)) {
961
- this.navigate({ ...err, replace: true });
962
- }
963
- this.load();
964
- });
965
- }
966
- if (latestPromise = this.checkLatest(promise)) {
967
- return latestPromise;
968
- }
969
- const exitingMatches = previousMatches.filter(
970
- (match) => !pendingMatches.find((d) => d.id === match.id)
971
- );
972
- const enteringMatches = pendingMatches.filter(
973
- (match) => !previousMatches.find((d) => d.id === match.id)
974
- );
975
- const stayingMatches = previousMatches.filter(
976
- (match) => pendingMatches.find((d) => d.id === match.id)
977
- );
978
- const shouldViewTransition = this.shouldViewTransition ?? this.options.defaultViewTransition;
979
- delete this.shouldViewTransition;
980
- const apply = () => {
981
- this.__store.batch(() => {
982
- this.__store.setState((s) => ({
983
- ...s,
984
- isLoading: false,
985
- matches: s.pendingMatches,
986
- pendingMatches: void 0,
987
- cachedMatches: [
988
- ...s.cachedMatches,
989
- ...exitingMatches.filter((d) => d.status !== "error")
990
- ],
991
- statusCode: (redirect == null ? void 0 : redirect.statusCode) || notFound ? 404 : s.matches.some((d) => d.status === "error") ? 500 : 200,
992
- redirect
993
- }));
994
- this.cleanCache();
995
- });
996
- [
997
- [exitingMatches, "onLeave"],
998
- [enteringMatches, "onEnter"],
999
- [stayingMatches, "onStay"]
1000
- ].forEach(([matches, hook]) => {
1001
- matches.forEach((match) => {
1002
- var _a2, _b2;
1003
- (_b2 = (_a2 = this.looseRoutesById[match.routeId].options)[hook]) == null ? void 0 : _b2.call(_a2, match);
1004
- });
1005
- });
1006
- resolveLoad();
1007
- };
1008
- ((_b = (_a = shouldViewTransition && typeof document !== "undefined" ? document : void 0) == null ? void 0 : _a.startViewTransition) == null ? void 0 : _b.call(_a, apply)) || apply();
1009
- } catch (err) {
1010
- if (latestPromise = this.checkLatest(promise)) {
1011
- return latestPromise;
1012
- }
1013
- console.error("Load Error", err);
1014
- rejectLoad(err);
1015
- }
1016
- });
1017
- return this.latestLoadPromise;
1018
- };
1019
1010
  this.resolveRedirect = (err) => {
1020
1011
  const redirect = err;
1021
1012
  if (!redirect.href) {