@tanstack/react-router 1.30.1 → 1.31.1

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.
Files changed (58) hide show
  1. package/dist/cjs/Matches.cjs.map +1 -1
  2. package/dist/cjs/Matches.d.cts +10 -10
  3. package/dist/cjs/RouterProvider.cjs.map +1 -1
  4. package/dist/cjs/RouterProvider.d.cts +7 -7
  5. package/dist/cjs/fileRoute.d.cts +1 -1
  6. package/dist/cjs/link.cjs.map +1 -1
  7. package/dist/cjs/link.d.cts +42 -42
  8. package/dist/cjs/redirects.cjs.map +1 -1
  9. package/dist/cjs/redirects.d.cts +5 -6
  10. package/dist/cjs/route.cjs.map +1 -1
  11. package/dist/cjs/route.d.cts +6 -6
  12. package/dist/cjs/routeInfo.d.cts +19 -2
  13. package/dist/cjs/router.cjs +69 -52
  14. package/dist/cjs/router.cjs.map +1 -1
  15. package/dist/cjs/router.d.cts +15 -15
  16. package/dist/cjs/routerContext.cjs.map +1 -1
  17. package/dist/cjs/routerContext.d.cts +1 -1
  18. package/dist/cjs/useNavigate.cjs.map +1 -1
  19. package/dist/cjs/useNavigate.d.cts +3 -4
  20. package/dist/cjs/useRouter.cjs.map +1 -1
  21. package/dist/cjs/useRouter.d.cts +3 -4
  22. package/dist/cjs/useRouterState.cjs.map +1 -1
  23. package/dist/cjs/useRouterState.d.cts +3 -4
  24. package/dist/esm/Matches.d.ts +10 -10
  25. package/dist/esm/Matches.js.map +1 -1
  26. package/dist/esm/RouterProvider.d.ts +7 -7
  27. package/dist/esm/RouterProvider.js.map +1 -1
  28. package/dist/esm/fileRoute.d.ts +1 -1
  29. package/dist/esm/link.d.ts +42 -42
  30. package/dist/esm/link.js.map +1 -1
  31. package/dist/esm/redirects.d.ts +5 -6
  32. package/dist/esm/redirects.js.map +1 -1
  33. package/dist/esm/route.d.ts +6 -6
  34. package/dist/esm/route.js.map +1 -1
  35. package/dist/esm/routeInfo.d.ts +19 -2
  36. package/dist/esm/router.d.ts +15 -15
  37. package/dist/esm/router.js +69 -52
  38. package/dist/esm/router.js.map +1 -1
  39. package/dist/esm/routerContext.d.ts +1 -1
  40. package/dist/esm/routerContext.js.map +1 -1
  41. package/dist/esm/useNavigate.d.ts +3 -4
  42. package/dist/esm/useNavigate.js.map +1 -1
  43. package/dist/esm/useRouter.d.ts +3 -4
  44. package/dist/esm/useRouter.js.map +1 -1
  45. package/dist/esm/useRouterState.d.ts +3 -4
  46. package/dist/esm/useRouterState.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/Matches.tsx +40 -22
  49. package/src/RouterProvider.tsx +34 -11
  50. package/src/link.tsx +124 -139
  51. package/src/redirects.ts +14 -14
  52. package/src/route.ts +3 -3
  53. package/src/routeInfo.ts +72 -4
  54. package/src/router.ts +132 -68
  55. package/src/routerContext.tsx +1 -1
  56. package/src/useNavigate.tsx +9 -10
  57. package/src/useRouter.tsx +4 -5
  58. package/src/useRouterState.tsx +5 -6
@@ -3,7 +3,7 @@ import type * as React from 'react';
3
3
  import type { HistoryState, RouterHistory } from '@tanstack/history';
4
4
  import type { AnyContext, AnyRoute, AnySearchSchema, ErrorRouteComponent, NotFoundRouteComponent, RouteMask } from './route.js';
5
5
  import type { FullSearchSchema, RouteById, RoutePaths, RoutesById, RoutesByPath } from './routeInfo.js';
6
- import type { ControlledPromise, NonNullableUpdater, PickAsRequired, Timeout, Updater } from './utils.js';
6
+ import type { ControlledPromise, NonNullableUpdater, PickAsRequired, Updater } from './utils.js';
7
7
  import type { RouteComponent } from './route.js';
8
8
  import type { AnyRouteMatch, MakeRouteMatch, MatchRouteOptions } from './Matches.js';
9
9
  import type { ParsedLocation } from './location.js';
@@ -19,12 +19,12 @@ declare global {
19
19
  __TSR_DEHYDRATED__?: {
20
20
  data: string;
21
21
  };
22
- __TSR_ROUTER_CONTEXT__?: React.Context<Router<any>>;
22
+ __TSR_ROUTER_CONTEXT__?: React.Context<Router<any, any>>;
23
23
  }
24
24
  }
25
25
  export interface Register {
26
26
  }
27
- export type AnyRouter = Router<AnyRoute, any, any>;
27
+ export type AnyRouter = Router<any, any, any, any>;
28
28
  export type RegisteredRouter = Register extends {
29
29
  router: infer TRouter extends AnyRouter;
30
30
  } ? TRouter : AnyRouter;
@@ -37,7 +37,8 @@ export type RouterContextOptions<TRouteTree extends AnyRoute> = AnyContext exten
37
37
  } : {
38
38
  context: TRouteTree['types']['routerContext'];
39
39
  };
40
- export interface RouterOptions<TRouteTree extends AnyRoute, TDehydrated extends Record<string, any> = Record<string, any>, TSerializedError extends Record<string, any> = Record<string, any>> {
40
+ export type TrailingSlashOption = 'always' | 'never' | 'preserve';
41
+ export interface RouterOptions<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption, TDehydrated extends Record<string, any> = Record<string, any>, TSerializedError extends Record<string, any> = Record<string, any>> {
41
42
  history?: RouterHistory;
42
43
  stringifySearch?: SearchSerializer;
43
44
  parseSearch?: SearchParser;
@@ -77,7 +78,7 @@ export interface RouterOptions<TRouteTree extends AnyRoute, TDehydrated extends
77
78
  defaultNotFoundComponent?: NotFoundRouteComponent;
78
79
  transformer?: RouterTransformer;
79
80
  errorSerializer?: RouterErrorSerializer<TSerializedError>;
80
- trailingSlash?: 'always' | 'never' | 'preserve';
81
+ trailingSlash?: TTrailingSlashOption;
81
82
  }
82
83
  export interface RouterTransformer {
83
84
  stringify: (obj: unknown) => string;
@@ -115,7 +116,7 @@ export interface BuildNextOptions {
115
116
  unmaskOnReload?: boolean;
116
117
  };
117
118
  from?: string;
118
- _fromLocation?: ParsedLocation;
119
+ fromSearch?: unknown;
119
120
  }
120
121
  export interface DehydratedRouterState {
121
122
  dehydratedMatches: Array<DehydratedRouteMatch>;
@@ -124,7 +125,7 @@ export type DehydratedRouteMatch = Pick<MakeRouteMatch, 'id' | 'status' | 'updat
124
125
  export interface DehydratedRouter {
125
126
  state: DehydratedRouterState;
126
127
  }
127
- export type RouterConstructorOptions<TRouteTree extends AnyRoute, TDehydrated extends Record<string, any>, TSerializedError extends Record<string, any>> = Omit<RouterOptions<TRouteTree, TDehydrated, TSerializedError>, 'context'> & RouterContextOptions<TRouteTree>;
128
+ export type RouterConstructorOptions<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption, TDehydrated extends Record<string, any>, TSerializedError extends Record<string, any>> = Omit<RouterOptions<TRouteTree, TTrailingSlashOption, TDehydrated, TSerializedError>, 'context'> & RouterContextOptions<TRouteTree>;
128
129
  export declare const componentTypes: readonly ["component", "errorComponent", "pendingComponent", "notFoundComponent"];
129
130
  export type RouterEvents = {
130
131
  onBeforeLoad: {
@@ -151,19 +152,18 @@ export type RouterListener<TRouterEvent extends RouterEvent> = {
151
152
  eventType: TRouterEvent['type'];
152
153
  fn: ListenerFn<TRouterEvent>;
153
154
  };
154
- export declare function createRouter<TRouteTree extends AnyRoute = AnyRoute, TDehydrated extends Record<string, any> = Record<string, any>, TSerializedError extends Record<string, any> = Record<string, any>>(options: RouterConstructorOptions<TRouteTree, TDehydrated, TSerializedError>): Router<TRouteTree, TDehydrated, TSerializedError>;
155
- export declare class Router<in out TRouteTree extends AnyRoute = AnyRoute, in out TDehydrated extends Record<string, any> = Record<string, any>, in out TSerializedError extends Record<string, any> = Record<string, any>> {
155
+ export declare function createRouter<TRouteTree extends AnyRoute, TTrailingSlashOption extends TrailingSlashOption, TDehydrated extends Record<string, any> = Record<string, any>, TSerializedError extends Record<string, any> = Record<string, any>>(options: RouterConstructorOptions<TRouteTree, TTrailingSlashOption, TDehydrated, TSerializedError>): Router<TRouteTree, TTrailingSlashOption, TDehydrated, TSerializedError>;
156
+ export declare class Router<in out TRouteTree extends AnyRoute, in out TTrailingSlashOption extends TrailingSlashOption, in out TDehydrated extends Record<string, any> = Record<string, any>, in out TSerializedError extends Record<string, any> = Record<string, any>> {
156
157
  tempLocationKey: string | undefined;
157
158
  resetNextScroll: boolean;
158
159
  shouldViewTransition?: true;
159
- navigateTimeout: Timeout | null;
160
160
  latestLoadPromise: Promise<void>;
161
161
  subscribers: Set<RouterListener<RouterEvent>>;
162
162
  injectedHtml: Array<InjectedHtmlEntry>;
163
163
  dehydratedData?: TDehydrated;
164
164
  viewTransitionPromise?: ControlledPromise<true>;
165
165
  __store: Store<RouterState<TRouteTree>>;
166
- options: PickAsRequired<Omit<RouterOptions<TRouteTree, TDehydrated, TSerializedError>, 'transformer'> & {
166
+ options: PickAsRequired<Omit<RouterOptions<TRouteTree, TTrailingSlashOption, TDehydrated, TSerializedError>, 'transformer'> & {
167
167
  transformer: RouterTransformer;
168
168
  }, 'stringifySearch' | 'parseSearch' | 'context'>;
169
169
  history: RouterHistory;
@@ -176,10 +176,10 @@ export declare class Router<in out TRouteTree extends AnyRoute = AnyRoute, in ou
176
176
  /**
177
177
  * @deprecated Use the `createRouter` function instead
178
178
  */
179
- constructor(options: RouterConstructorOptions<TRouteTree, TDehydrated, TSerializedError>);
179
+ constructor(options: RouterConstructorOptions<TRouteTree, TTrailingSlashOption, TDehydrated, TSerializedError>);
180
180
  isServer: boolean;
181
181
  startReactTransition: (fn: () => void) => void;
182
- update: (newOptions: RouterConstructorOptions<TRouteTree, TDehydrated, TSerializedError>) => void;
182
+ update: (newOptions: RouterConstructorOptions<TRouteTree, TTrailingSlashOption, TDehydrated, TSerializedError>) => void;
183
183
  get state(): RouterState<TRouteTree, MakeRouteMatch<TRouteTree>>;
184
184
  buildRouteTree: () => void;
185
185
  subscribe: <TType extends keyof RouterEvents>(eventType: TType, fn: ListenerFn<RouterEvents[TType]>) => () => void;
@@ -208,8 +208,8 @@ export declare class Router<in out TRouteTree extends AnyRoute = AnyRoute, in ou
208
208
  load: () => Promise<void>;
209
209
  resolveRedirect: (err: AnyRedirect) => ResolvedRedirect;
210
210
  cleanCache: () => void;
211
- preloadRoute: <TFrom extends string | import("./routeInfo").ParseRoute<TRouteTree, TRouteTree>["fullPath"] = string, TTo extends string = "", TMaskFrom extends string | import("./routeInfo").ParseRoute<TRouteTree, TRouteTree>["fullPath"] = TFrom, TMaskTo extends string = "">(opts: NavigateOptions<TRouteTree, TFrom, TTo, TMaskFrom, TMaskTo>) => Promise<Array<AnyRouteMatch> | undefined>;
212
- matchRoute: <TFrom extends RoutePaths<TRouteTree> = "/", TTo extends string = "", TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>>(location: ToOptions<TRouteTree, TFrom, TTo>, opts?: MatchRouteOptions) => false | RouteById<TRouteTree, TResolved>["types"]["allParams"];
211
+ preloadRoute: <TFrom extends string | import("./routeInfo").ParseRoute<TRouteTree, TRouteTree>["fullPath"] = string, TTo extends string = "", TMaskFrom extends string | import("./routeInfo").ParseRoute<TRouteTree, TRouteTree>["fullPath"] = TFrom, TMaskTo extends string = "">(opts: NavigateOptions<Router<TRouteTree, TTrailingSlashOption, TDehydrated, TSerializedError>, TFrom, TTo, TMaskFrom, TMaskTo>) => Promise<Array<AnyRouteMatch> | undefined>;
212
+ matchRoute: <TFrom extends RoutePaths<TRouteTree> = "/", TTo extends string = "", TResolved = ResolveRelativePath<TFrom, NoInfer<TTo>>>(location: ToOptions<Router<TRouteTree, TTrailingSlashOption, TDehydrated, TSerializedError>, TFrom, TTo>, opts?: MatchRouteOptions) => false | RouteById<TRouteTree, TResolved>["types"]["allParams"];
213
213
  injectHtml: (html: string | (() => Promise<string> | string)) => Promise<void>;
214
214
  registeredDeferredsIds: Map<string, {}>;
215
215
  registeredDeferreds: WeakMap<{}, DeferredPromiseState<any>>;
@@ -28,7 +28,6 @@ class Router {
28
28
  )}`;
29
29
  this.resetNextScroll = true;
30
30
  this.shouldViewTransition = void 0;
31
- this.navigateTimeout = null;
32
31
  this.latestLoadPromise = Promise.resolve();
33
32
  this.subscribers = /* @__PURE__ */ new Set();
34
33
  this.injectedHtml = [];
@@ -396,20 +395,20 @@ class Router {
396
395
  };
397
396
  this.buildLocation = (opts) => {
398
397
  const build = (dest = {}, matches) => {
399
- var _a, _b, _c, _d;
400
- const fromPath = dest.from || this.latestLocation.pathname;
401
- let fromSearch = ((_a = dest._fromLocation) == null ? void 0 : _a.search) || this.latestLocation.search;
402
- const fromMatches = this.matchRoutes(fromPath, fromSearch);
398
+ var _a, _b, _c;
399
+ let fromPath = this.latestLocation.pathname;
400
+ let fromSearch = dest.fromSearch || this.latestLocation.search;
401
+ const fromMatches = this.matchRoutes(
402
+ this.latestLocation.pathname,
403
+ fromSearch
404
+ );
405
+ fromPath = ((_a = fromMatches.find((d) => d.id === dest.from)) == null ? void 0 : _a.pathname) || fromPath;
403
406
  fromSearch = ((_b = last(fromMatches)) == null ? void 0 : _b.search) || this.latestLocation.search;
404
407
  const stayingMatches = matches == null ? void 0 : matches.filter(
405
408
  (d) => fromMatches.find((e) => e.routeId === d.routeId)
406
409
  );
407
- const fromRoute = this.looseRoutesById[(_c = last(fromMatches)) == null ? void 0 : _c.routeId];
408
- let pathname = dest.to ? this.resolvePathWithBase(
409
- dest.from ?? this.latestLocation.pathname,
410
- `${dest.to}`
411
- ) : this.resolvePathWithBase(fromRoute == null ? void 0 : fromRoute.fullPath, fromRoute == null ? void 0 : fromRoute.fullPath);
412
- const prevParams = { ...(_d = last(fromMatches)) == null ? void 0 : _d.params };
410
+ let pathname = dest.to ? this.resolvePathWithBase(fromPath, `${dest.to}`) : this.resolvePathWithBase(fromPath, fromPath);
411
+ const prevParams = { ...(_c = last(fromMatches)) == null ? void 0 : _c.params };
413
412
  let nextParams = (dest.params ?? true) === true ? prevParams : { ...prevParams, ...functionalUpdate(dest.params, prevParams) };
414
413
  if (Object.keys(nextParams).length > 0) {
415
414
  matches == null ? void 0 : matches.map((d) => this.looseRoutesById[d.routeId].options.stringifyParams).filter(Boolean).forEach((fn) => {
@@ -503,8 +502,6 @@ class Router {
503
502
  viewTransition,
504
503
  ...next
505
504
  }) => {
506
- if (this.navigateTimeout)
507
- clearTimeout(this.navigateTimeout);
508
505
  const isSameUrl = this.latestLocation.href === next.href;
509
506
  if (!isSameUrl) {
510
507
  let { maskedLocation, ...nextHistory } = next;
@@ -618,14 +615,10 @@ class Router {
618
615
  err.routeId = match.routeId;
619
616
  }
620
617
  if (isRedirect(err)) {
621
- const redirect = this.resolveRedirect(err);
622
- if (!preload && !this.isServer) {
623
- this.navigate({ ...redirect, replace: true });
624
- }
625
- throw redirect;
618
+ err = this.resolveRedirect(err);
619
+ throw err;
626
620
  } else if (isNotFound(err)) {
627
- if (!preload)
628
- this.handleNotFound(matches, err);
621
+ this.handleNotFound(matches, err);
629
622
  throw err;
630
623
  }
631
624
  }
@@ -634,6 +627,20 @@ class Router {
634
627
  const parentMatch = matches[index - 1];
635
628
  const route = this.looseRoutesById[match.routeId];
636
629
  const abortController = new AbortController();
630
+ let loadPromise = match.loadPromise;
631
+ if (match.isFetching) {
632
+ continue;
633
+ }
634
+ const previousResolve = loadPromise.resolve;
635
+ loadPromise = createControlledPromise(
636
+ // Resolve the old when we we resolve the new one
637
+ previousResolve
638
+ );
639
+ matches[index] = match = updateMatch(match.id, (prev) => ({
640
+ ...prev,
641
+ isFetching: "beforeLoad",
642
+ loadPromise
643
+ }));
637
644
  const handleSerialError = (err, routerCode) => {
638
645
  var _a2, _b2;
639
646
  err.routerCode = routerCode;
@@ -685,6 +692,8 @@ class Router {
685
692
  buildLocation: this.buildLocation,
686
693
  cause: preload ? "preload" : match.cause
687
694
  })) ?? {};
695
+ if (latestPromise = checkLatest())
696
+ return latestPromise;
688
697
  if (isRedirect(beforeLoadContext) || isNotFound(beforeLoadContext)) {
689
698
  handleSerialError(beforeLoadContext, "BEFORE_LOAD");
690
699
  }
@@ -708,6 +717,8 @@ class Router {
708
717
  updateMatch(match.id, () => match);
709
718
  }
710
719
  }
720
+ if (latestPromise = checkLatest())
721
+ return latestPromise;
711
722
  const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
712
723
  const matchPromises = [];
713
724
  await Promise.all(
@@ -732,7 +743,6 @@ class Router {
732
743
  let lazyPromise = Promise.resolve();
733
744
  let componentsPromise = Promise.resolve();
734
745
  let loaderPromise = existing.loaderPromise;
735
- let loadPromise = existing.loadPromise;
736
746
  const potentialPendingMinPromise = async () => {
737
747
  const latestMatch = getRouteMatch(this.state, match.id);
738
748
  if (latestMatch == null ? void 0 : latestMatch.minPendingPromise) {
@@ -746,12 +756,15 @@ class Router {
746
756
  }
747
757
  };
748
758
  try {
749
- if (!match.isFetching) {
750
- matches[index] = match = {
751
- ...match,
752
- isFetching: true,
753
- fetchCount: match.fetchCount + 1
754
- };
759
+ if (match.isFetching === "beforeLoad") {
760
+ matches[index] = match = updateMatch(
761
+ match.id,
762
+ (prev) => ({
763
+ ...prev,
764
+ isFetching: "loader",
765
+ fetchCount: match.fetchCount + 1
766
+ })
767
+ );
755
768
  lazyPromise = ((_a2 = route.lazyFn) == null ? void 0 : _a2.call(route).then((lazyRoute) => {
756
769
  Object.assign(route.options, lazyRoute.options);
757
770
  })) || Promise.resolve();
@@ -769,17 +782,14 @@ class Router {
769
782
  if (latestPromise = checkLatest())
770
783
  return await latestPromise;
771
784
  loaderPromise = (_c = (_b2 = route.options).loader) == null ? void 0 : _c.call(_b2, loaderContext);
772
- const previousResolve = loadPromise.resolve;
773
- loadPromise = createControlledPromise(
774
- // Resolve the old when we we resolve the new one
775
- previousResolve
785
+ matches[index] = match = updateMatch(
786
+ match.id,
787
+ (prev) => ({
788
+ ...prev,
789
+ loaderPromise
790
+ })
776
791
  );
777
792
  }
778
- matches[index] = match = updateMatch(match.id, (prev) => ({
779
- ...prev,
780
- loaderPromise,
781
- loadPromise
782
- }));
783
793
  const loaderData = await loaderPromise;
784
794
  if (latestPromise = checkLatest())
785
795
  return await latestPromise;
@@ -830,7 +840,7 @@ class Router {
830
840
  await componentsPromise;
831
841
  if (latestPromise = checkLatest())
832
842
  return await latestPromise;
833
- loadPromise.resolve();
843
+ match.loadPromise.resolve();
834
844
  };
835
845
  const age = Date.now() - match.updatedAt;
836
846
  const staleAge = preload ? route.options.preloadStaleTime ?? this.options.defaultPreloadStaleTime ?? 3e4 : route.options.staleTime ?? this.options.defaultStaleTime ?? 0;
@@ -930,21 +940,28 @@ class Router {
930
940
  });
931
941
  let redirect;
932
942
  let notFound;
933
- try {
934
- const loadMatchesPromise = this.loadMatches({
935
- matches: pendingMatches,
936
- location: next,
937
- checkLatest: () => this.checkLatest(promise)
938
- });
939
- if (previousMatches.length || this.isServer) {
940
- await loadMatchesPromise;
941
- }
942
- } catch (err) {
943
- if (isRedirect(err)) {
944
- redirect = err;
945
- } else if (isNotFound(err)) {
946
- notFound = err;
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
+ }
947
957
  }
958
+ } else {
959
+ loadMatches().catch((err) => {
960
+ if (isRedirect(err)) {
961
+ this.navigate({ ...err, replace: true });
962
+ }
963
+ this.load();
964
+ });
948
965
  }
949
966
  if (latestPromise = this.checkLatest(promise)) {
950
967
  return latestPromise;
@@ -1061,7 +1078,7 @@ class Router {
1061
1078
  } catch (err) {
1062
1079
  if (isRedirect(err)) {
1063
1080
  return await this.preloadRoute({
1064
- _fromDest: next,
1081
+ fromSearch: next.search,
1065
1082
  from: next.pathname,
1066
1083
  ...err
1067
1084
  });