@tanstack/react-router 1.12.16 → 1.14.0

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.
@@ -2,16 +2,19 @@
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const history = require("@tanstack/history");
4
4
  const reactStore = require("@tanstack/react-store");
5
+ const route = require("./route.cjs");
5
6
  const searchParams = require("./searchParams.cjs");
6
7
  const utils = require("./utils.cjs");
7
8
  const RouterProvider = require("./RouterProvider.cjs");
8
9
  const path = require("./path.cjs");
9
10
  const invariant = require("tiny-invariant");
10
11
  const redirects = require("./redirects.cjs");
12
+ const notFound = require("./not-found.cjs");
11
13
  const componentTypes = [
12
14
  "component",
13
15
  "errorComponent",
14
- "pendingComponent"
16
+ "pendingComponent",
17
+ "notFoundComponent"
15
18
  ];
16
19
  function createRouter(options) {
17
20
  return new Router(options);
@@ -28,6 +31,11 @@ class Router {
28
31
  this.injectedHtml = [];
29
32
  this.startReactTransition = (fn) => fn();
30
33
  this.update = (newOptions) => {
34
+ if (newOptions.notFoundRoute) {
35
+ console.warn(
36
+ "The notFoundRoute API is deprecated and will be removed in the next major version. See https://tanstack.com/router/v1/docs/guide/not-found-errors#migrating-from-notfoundroute for more info."
37
+ );
38
+ }
31
39
  const previousOptions = this.options;
32
40
  this.options = {
33
41
  ...this.options,
@@ -193,13 +201,13 @@ class Router {
193
201
  };
194
202
  this.matchRoutes = (pathname, locationSearch, opts) => {
195
203
  let routeParams = {};
196
- let foundRoute = this.flatRoutes.find((route) => {
204
+ let foundRoute = this.flatRoutes.find((route2) => {
197
205
  const matchedParams = path.matchPathname(
198
206
  this.basepath,
199
207
  path.trimPathRight(pathname),
200
208
  {
201
- to: route.fullPath,
202
- caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive,
209
+ to: route2.fullPath,
210
+ caseSensitive: route2.options.caseSensitive ?? this.options.caseSensitive,
203
211
  fuzzy: true
204
212
  }
205
213
  );
@@ -211,26 +219,30 @@ class Router {
211
219
  });
212
220
  let routeCursor = foundRoute || this.routesById["__root__"];
213
221
  let matchedRoutes = [routeCursor];
222
+ let isGlobalNotFound = false;
214
223
  if (
215
224
  // If we found a route, and it's not an index route and we have left over path
216
- (foundRoute ? foundRoute.path !== "/" && routeParams["**"] : (
225
+ foundRoute ? foundRoute.path !== "/" && routeParams["**"] : (
217
226
  // Or if we didn't find a route and we have left over path
218
227
  path.trimPathRight(pathname)
219
- )) && // And we have a 404 route configured
220
- this.options.notFoundRoute
228
+ )
221
229
  ) {
222
- matchedRoutes.push(this.options.notFoundRoute);
230
+ if (this.options.notFoundRoute) {
231
+ matchedRoutes.push(this.options.notFoundRoute);
232
+ } else {
233
+ isGlobalNotFound = true;
234
+ }
223
235
  }
224
236
  while (routeCursor == null ? void 0 : routeCursor.parentRoute) {
225
237
  routeCursor = routeCursor.parentRoute;
226
238
  if (routeCursor)
227
239
  matchedRoutes.unshift(routeCursor);
228
240
  }
229
- const parseErrors = matchedRoutes.map((route) => {
241
+ const parseErrors = matchedRoutes.map((route2) => {
230
242
  let parsedParamsError;
231
- if (route.options.parseParams) {
243
+ if (route2.options.parseParams) {
232
244
  try {
233
- const parsedParams = route.options.parseParams(routeParams);
245
+ const parsedParams = route2.options.parseParams(routeParams);
234
246
  Object.assign(routeParams, parsedParams);
235
247
  } catch (err) {
236
248
  parsedParamsError = new PathParamError(err.message, {
@@ -245,13 +257,13 @@ class Router {
245
257
  return;
246
258
  });
247
259
  const matches = [];
248
- matchedRoutes.forEach((route, index) => {
260
+ matchedRoutes.forEach((route$1, index) => {
249
261
  var _a, _b, _c, _d, _e, _f;
250
262
  const parentMatch = matches[index - 1];
251
263
  const [preMatchSearch, searchError] = (() => {
252
264
  const parentSearch = (parentMatch == null ? void 0 : parentMatch.search) ?? locationSearch;
253
265
  try {
254
- const validator = typeof route.options.validateSearch === "object" ? route.options.validateSearch.parse : route.options.validateSearch;
266
+ const validator = typeof route$1.options.validateSearch === "object" ? route$1.options.validateSearch.parse : route$1.options.validateSearch;
255
267
  let search = (validator == null ? void 0 : validator(parentSearch)) ?? {};
256
268
  return [
257
269
  {
@@ -270,28 +282,32 @@ class Router {
270
282
  return [parentSearch, searchError2];
271
283
  }
272
284
  })();
273
- const loaderDeps = ((_b = (_a = route.options).loaderDeps) == null ? void 0 : _b.call(_a, {
285
+ const loaderDeps = ((_b = (_a = route$1.options).loaderDeps) == null ? void 0 : _b.call(_a, {
274
286
  search: preMatchSearch
275
287
  })) ?? "";
276
288
  const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : "";
277
289
  const interpolatedPath = path.interpolatePath({
278
- path: route.fullPath,
290
+ path: route$1.fullPath,
279
291
  params: routeParams
280
292
  });
281
293
  const matchId = path.interpolatePath({
282
- path: route.id,
294
+ path: route$1.id,
283
295
  params: routeParams,
284
296
  leaveWildcards: true
285
297
  }) + loaderDepsHash;
286
298
  const existingMatch = RouterProvider.getRouteMatch(this.state, matchId);
287
299
  const cause = this.state.matches.find((d) => d.id === matchId) ? "stay" : "enter";
288
- const hasLoaders = !!(route.options.loader || route.lazyFn || componentTypes.some((d) => {
300
+ const hasLoaders = !!(route$1.options.loader || route$1.lazyFn || componentTypes.some((d) => {
289
301
  var _a2;
290
- return (_a2 = route.options[d]) == null ? void 0 : _a2.preload;
302
+ return (_a2 = route$1.options[d]) == null ? void 0 : _a2.preload;
291
303
  }));
292
- const match = existingMatch ? { ...existingMatch, cause } : {
304
+ const match = existingMatch ? {
305
+ ...existingMatch,
306
+ cause,
307
+ notFoundError: isGlobalNotFound && route$1.id === route.rootRouteId ? { global: true } : void 0
308
+ } : {
293
309
  id: matchId,
294
- routeId: route.id,
310
+ routeId: route$1.id,
295
311
  params: routeParams,
296
312
  pathname: path.joinPaths([this.basepath, interpolatedPath]),
297
313
  updatedAt: Date.now(),
@@ -311,9 +327,10 @@ class Router {
311
327
  loaderDeps,
312
328
  invalid: false,
313
329
  preload: false,
314
- links: (_d = (_c = route.options).links) == null ? void 0 : _d.call(_c),
315
- scripts: (_f = (_e = route.options).scripts) == null ? void 0 : _f.call(_e),
316
- staticData: route.options.staticData || {}
330
+ notFoundError: isGlobalNotFound && route$1.id === route.rootRouteId ? { global: true } : void 0,
331
+ links: (_d = (_c = route$1.options).links) == null ? void 0 : _d.call(_c),
332
+ scripts: (_f = (_e = route$1.options).scripts) == null ? void 0 : _f.call(_e),
333
+ staticData: route$1.options.staticData || {}
317
334
  };
318
335
  match.search = utils.replaceEqualDeep(match.search, preMatchSearch);
319
336
  match.searchError = searchError;
@@ -539,7 +556,7 @@ class Router {
539
556
  try {
540
557
  for (let [index, match] of matches.entries()) {
541
558
  const parentMatch = matches[index - 1];
542
- const route = this.looseRoutesById[match.routeId];
559
+ const route2 = this.looseRoutesById[match.routeId];
543
560
  const abortController = new AbortController();
544
561
  const handleErrorAndRedirect = (err, code) => {
545
562
  var _a2, _b2;
@@ -548,8 +565,11 @@ class Router {
548
565
  if (redirects.isRedirect(err)) {
549
566
  throw err;
550
567
  }
568
+ if (notFound.isNotFound(err)) {
569
+ this.updateMatchesWithNotFound(matches, match, err);
570
+ }
551
571
  try {
552
- (_b2 = (_a2 = route.options).onError) == null ? void 0 : _b2.call(_a2, err);
572
+ (_b2 = (_a2 = route2.options).onError) == null ? void 0 : _b2.call(_a2, err);
553
573
  } catch (errorHandlerErr) {
554
574
  err = errorHandlerErr;
555
575
  if (redirects.isRedirect(errorHandlerErr)) {
@@ -572,9 +592,9 @@ class Router {
572
592
  handleErrorAndRedirect(match.searchError, "VALIDATE_SEARCH");
573
593
  }
574
594
  const parentContext = (parentMatch == null ? void 0 : parentMatch.context) ?? this.options.context ?? {};
575
- const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
595
+ const pendingMs = route2.options.pendingMs ?? this.options.defaultPendingMs;
576
596
  const pendingPromise = typeof pendingMs === "number" && pendingMs <= 0 ? Promise.resolve() : new Promise((r) => setTimeout(r, pendingMs));
577
- const beforeLoadContext = await ((_b = (_a = route.options).beforeLoad) == null ? void 0 : _b.call(_a, {
597
+ const beforeLoadContext = await ((_b = (_a = route2.options).beforeLoad) == null ? void 0 : _b.call(_a, {
578
598
  search: match.search,
579
599
  abortController,
580
600
  params: match.params,
@@ -623,7 +643,7 @@ class Router {
623
643
  new Promise(async (resolve) => {
624
644
  var _a2;
625
645
  const parentMatchPromise = matchPromises[index - 1];
626
- const route = this.looseRoutesById[match.routeId];
646
+ const route2 = this.looseRoutesById[match.routeId];
627
647
  const handleErrorAndRedirect = (err) => {
628
648
  if (redirects.isRedirect(err)) {
629
649
  if (!preload) {
@@ -631,6 +651,9 @@ class Router {
631
651
  }
632
652
  return true;
633
653
  }
654
+ if (notFound.isNotFound(err)) {
655
+ this.updateMatchesWithNotFound(matches, match, err);
656
+ }
634
657
  return false;
635
658
  };
636
659
  let loadPromise;
@@ -639,9 +662,9 @@ class Router {
639
662
  showPending: false
640
663
  };
641
664
  let didShowPending = false;
642
- const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
643
- const pendingMinMs = route.options.pendingMinMs ?? this.options.defaultPendingMinMs;
644
- const shouldPending = !preload && typeof pendingMs === "number" && (route.options.pendingComponent ?? this.options.defaultPendingComponent);
665
+ const pendingMs = route2.options.pendingMs ?? this.options.defaultPendingMs;
666
+ const pendingMinMs = route2.options.pendingMinMs ?? this.options.defaultPendingMinMs;
667
+ const shouldPending = !preload && typeof pendingMs === "number" && (route2.options.pendingComponent ?? this.options.defaultPendingComponent);
645
668
  const loaderContext = {
646
669
  params: match.params,
647
670
  deps: match.loaderDeps,
@@ -666,20 +689,20 @@ class Router {
666
689
  isFetching: true,
667
690
  fetchCount: match.fetchCount + 1
668
691
  };
669
- const lazyPromise = ((_b2 = route.lazyFn) == null ? void 0 : _b2.call(route).then((lazyRoute) => {
670
- Object.assign(route.options, lazyRoute.options);
692
+ const lazyPromise = ((_b2 = route2.lazyFn) == null ? void 0 : _b2.call(route2).then((lazyRoute) => {
693
+ Object.assign(route2.options, lazyRoute.options);
671
694
  })) || Promise.resolve();
672
695
  const componentsPromise = lazyPromise.then(
673
696
  () => Promise.all(
674
697
  componentTypes.map(async (type) => {
675
- const component = route.options[type];
698
+ const component = route2.options[type];
676
699
  if (component == null ? void 0 : component.preload) {
677
700
  await component.preload();
678
701
  }
679
702
  })
680
703
  )
681
704
  );
682
- const loaderPromise = (_d = (_c = route.options).loader) == null ? void 0 : _d.call(_c, loaderContext);
705
+ const loaderPromise = (_d = (_c = route2.options).loader) == null ? void 0 : _d.call(_c, loaderContext);
683
706
  loadPromise = Promise.all([
684
707
  componentsPromise,
685
708
  loaderPromise,
@@ -704,7 +727,7 @@ class Router {
704
727
  }
705
728
  if (latestPromise = checkLatest())
706
729
  return await latestPromise;
707
- const meta = (_f = (_e = route.options).meta) == null ? void 0 : _f.call(_e, {
730
+ const meta = (_f = (_e = route2.options).meta) == null ? void 0 : _f.call(_e, {
708
731
  loaderData
709
732
  });
710
733
  matches[index] = match = {
@@ -723,7 +746,7 @@ class Router {
723
746
  if (handleErrorAndRedirect(error))
724
747
  return;
725
748
  try {
726
- (_h = (_g = route.options).onError) == null ? void 0 : _h.call(_g, error);
749
+ (_h = (_g = route2.options).onError) == null ? void 0 : _h.call(_g, error);
727
750
  } catch (onErrorError) {
728
751
  error = onErrorError;
729
752
  if (handleErrorAndRedirect(onErrorError))
@@ -739,9 +762,9 @@ class Router {
739
762
  updateMatch(match);
740
763
  };
741
764
  const age = Date.now() - match.updatedAt;
742
- let staleAge = preload ? route.options.preloadStaleTime ?? this.options.defaultPreloadStaleTime ?? 3e4 : route.options.staleTime ?? this.options.defaultStaleTime ?? 0;
765
+ let staleAge = preload ? route2.options.preloadStaleTime ?? this.options.defaultPreloadStaleTime ?? 3e4 : route2.options.staleTime ?? this.options.defaultStaleTime ?? 0;
743
766
  let shouldReload;
744
- const shouldReloadOption = route.options.shouldReload;
767
+ const shouldReloadOption = route2.options.shouldReload;
745
768
  shouldReload = typeof shouldReloadOption === "function" ? shouldReloadOption(loaderContext) : shouldReloadOption;
746
769
  matches[index] = match = {
747
770
  ...match,
@@ -883,11 +906,11 @@ class Router {
883
906
  return {
884
907
  ...s,
885
908
  cachedMatches: s.cachedMatches.filter((d) => {
886
- const route = this.looseRoutesById[d.routeId];
887
- if (!route.options.loader) {
909
+ const route2 = this.looseRoutesById[d.routeId];
910
+ if (!route2.options.loader) {
888
911
  return false;
889
912
  }
890
- const gcTime = (d.preload ? route.options.preloadGcTime ?? this.options.defaultPreloadGcTime : route.options.gcTime ?? this.options.defaultGcTime) ?? 5 * 60 * 1e3;
913
+ const gcTime = (d.preload ? route2.options.preloadGcTime ?? this.options.defaultPreloadGcTime : route2.options.gcTime ?? this.options.defaultGcTime) ?? 5 * 60 * 1e3;
891
914
  return d.status !== "error" && Date.now() - d.updatedAt < gcTime;
892
915
  })
893
916
  };
@@ -966,7 +989,7 @@ class Router {
966
989
  const data = typeof getData === "function" ? await getData() : getData;
967
990
  return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${utils.escapeJSON(
968
991
  strKey
969
- )}"] = ${JSON.stringify(data)}
992
+ )}"] = ${JSON.stringify(this.options.transformer.stringify(data))}
970
993
  ;(() => {
971
994
  var el = document.getElementById('${id}')
972
995
  el.parentElement.removeChild(el)
@@ -980,7 +1003,9 @@ class Router {
980
1003
  this.hydrateData = (key) => {
981
1004
  if (typeof document !== "undefined") {
982
1005
  const strKey = typeof key === "string" ? key : JSON.stringify(key);
983
- return window[`__TSR_DEHYDRATED__${strKey}`];
1006
+ return this.options.transformer.parse(
1007
+ window[`__TSR_DEHYDRATED__${strKey}`]
1008
+ );
984
1009
  }
985
1010
  return void 0;
986
1011
  };
@@ -990,7 +1015,15 @@ class Router {
990
1015
  return {
991
1016
  state: {
992
1017
  dehydratedMatches: this.state.matches.map((d) => ({
993
- ...utils.pick(d, ["id", "status", "updatedAt", "loaderData"]),
1018
+ ...utils.pick(d, [
1019
+ "id",
1020
+ "status",
1021
+ "updatedAt",
1022
+ "loaderData",
1023
+ // Not-founds that occur during SSR don't require the client to load data before
1024
+ // triggering in order to prevent the flicker of the loading component
1025
+ "notFoundError"
1026
+ ]),
994
1027
  // If an error occurs server-side during SSRing,
995
1028
  // send a small subset of the error to the client
996
1029
  error: d.error ? {
@@ -1002,24 +1035,24 @@ class Router {
1002
1035
  };
1003
1036
  };
1004
1037
  this.hydrate = async (__do_not_use_server_ctx) => {
1005
- var _a, _b;
1038
+ var _a, _b, _c;
1006
1039
  let _ctx = __do_not_use_server_ctx;
1007
1040
  if (typeof document !== "undefined") {
1008
- _ctx = window.__TSR_DEHYDRATED__;
1041
+ _ctx = (_a = window.__TSR_DEHYDRATED__) == null ? void 0 : _a.data;
1009
1042
  }
1010
1043
  invariant(
1011
1044
  _ctx,
1012
1045
  "Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?"
1013
1046
  );
1014
- const ctx = _ctx;
1047
+ const ctx = this.options.transformer.parse(_ctx);
1015
1048
  this.dehydratedData = ctx.payload;
1016
- (_b = (_a = this.options).hydrate) == null ? void 0 : _b.call(_a, ctx.payload);
1049
+ (_c = (_b = this.options).hydrate) == null ? void 0 : _c.call(_b, ctx.payload);
1017
1050
  const dehydratedState = ctx.router.state;
1018
1051
  let matches = this.matchRoutes(
1019
1052
  this.state.location.pathname,
1020
1053
  this.state.location.search
1021
1054
  ).map((match) => {
1022
- var _a2, _b2, _c, _d, _e, _f;
1055
+ var _a2, _b2, _c2, _d, _e, _f;
1023
1056
  const dehydratedMatch = dehydratedState.dehydratedMatches.find(
1024
1057
  (d) => d.id === match.id
1025
1058
  );
@@ -1028,15 +1061,15 @@ class Router {
1028
1061
  `Could not find a client-side match for dehydrated match with id: ${match.id}!`
1029
1062
  );
1030
1063
  if (dehydratedMatch) {
1031
- const route = this.looseRoutesById[match.routeId];
1064
+ const route2 = this.looseRoutesById[match.routeId];
1032
1065
  return {
1033
1066
  ...match,
1034
1067
  ...dehydratedMatch,
1035
- meta: (_b2 = (_a2 = route.options).meta) == null ? void 0 : _b2.call(_a2, {
1068
+ meta: (_b2 = (_a2 = route2.options).meta) == null ? void 0 : _b2.call(_a2, {
1036
1069
  loaderData: dehydratedMatch.loaderData
1037
1070
  }),
1038
- links: (_d = (_c = route.options).links) == null ? void 0 : _d.call(_c),
1039
- scripts: (_f = (_e = route.options).scripts) == null ? void 0 : _f.call(_e)
1071
+ links: (_d = (_c2 = route2.options).links) == null ? void 0 : _d.call(_c2),
1072
+ scripts: (_f = (_e = route2.options).scripts) == null ? void 0 : _f.call(_e)
1040
1073
  };
1041
1074
  }
1042
1075
  return match;
@@ -1049,6 +1082,31 @@ class Router {
1049
1082
  };
1050
1083
  });
1051
1084
  };
1085
+ this.updateMatchesWithNotFound = (matches, currentMatch, err) => {
1086
+ const matchesByRouteId = Object.fromEntries(
1087
+ matches.map((match) => [match.routeId, match])
1088
+ );
1089
+ if (err.global) {
1090
+ matchesByRouteId[route.rootRouteId].notFoundError = err;
1091
+ } else {
1092
+ let currentRoute = this.routesById[err.route ?? currentMatch.routeId];
1093
+ while (!currentRoute.options.notFoundComponent) {
1094
+ currentRoute = currentRoute == null ? void 0 : currentRoute.parentRoute;
1095
+ invariant(
1096
+ currentRoute,
1097
+ "Found invalid route tree while trying to find not-found handler."
1098
+ );
1099
+ if (currentRoute.id === route.rootRouteId)
1100
+ break;
1101
+ }
1102
+ const match = matchesByRouteId[currentRoute.id];
1103
+ invariant(match, "Could not find match for route: " + currentRoute.id);
1104
+ match.notFoundError = err;
1105
+ }
1106
+ };
1107
+ this.hasNotFoundMatch = () => {
1108
+ return this.__store.state.matches.some((d) => d.notFoundError);
1109
+ };
1052
1110
  this.update({
1053
1111
  defaultPreloadDelay: 50,
1054
1112
  defaultPendingMs: 1e3,
@@ -1056,7 +1114,8 @@ class Router {
1056
1114
  context: void 0,
1057
1115
  ...options,
1058
1116
  stringifySearch: (options == null ? void 0 : options.stringifySearch) ?? searchParams.defaultStringifySearch,
1059
- parseSearch: (options == null ? void 0 : options.parseSearch) ?? searchParams.defaultParseSearch
1117
+ parseSearch: (options == null ? void 0 : options.parseSearch) ?? searchParams.defaultParseSearch,
1118
+ transformer: (options == null ? void 0 : options.transformer) ?? JSON
1060
1119
  });
1061
1120
  }
1062
1121
  get state() {