@tanstack/react-router 0.0.1-beta.185 → 0.0.1-beta.187

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.
@@ -236,11 +236,13 @@
236
236
  };
237
237
  },
238
238
  push: (path, state) => {
239
+ assignKey(state);
239
240
  queueTask(() => {
240
241
  opts.pushState(path, state);
241
242
  });
242
243
  },
243
244
  replace: (path, state) => {
245
+ assignKey(state);
244
246
  queueTask(() => {
245
247
  opts.replaceState(path, state);
246
248
  });
@@ -277,6 +279,16 @@
277
279
  }
278
280
  };
279
281
  }
282
+ function assignKey(state) {
283
+ state.key = createRandomKey();
284
+ // if (state.__actualLocation) {
285
+ // state.__actualLocation.state = {
286
+ // ...state.__actualLocation.state,
287
+ // key,
288
+ // }
289
+ // }
290
+ }
291
+
280
292
  function createBrowserHistory(opts) {
281
293
  const getHref = opts?.getHref ?? (() => `${window.location.pathname}${window.location.search}${window.location.hash}`);
282
294
  const createHref = opts?.createHref ?? (path => path);
@@ -306,16 +318,10 @@
306
318
  };
307
319
  },
308
320
  pushState: (path, state) => {
309
- window.history.pushState({
310
- ...state,
311
- key: createRandomKey()
312
- }, '', createHref(path));
321
+ window.history.pushState(state, '', createHref(path));
313
322
  },
314
323
  replaceState: (path, state) => {
315
- window.history.replaceState({
316
- ...state,
317
- key: createRandomKey()
318
- }, '', createHref(path));
324
+ window.history.replaceState(state, '', createHref(path));
319
325
  },
320
326
  back: () => window.history.back(),
321
327
  forward: () => window.history.forward(),
@@ -334,24 +340,20 @@
334
340
  }) {
335
341
  const entries = opts.initialEntries;
336
342
  let index = opts.initialIndex ?? entries.length - 1;
337
- let currentState = {};
343
+ let currentState = {
344
+ key: createRandomKey()
345
+ };
338
346
  const getLocation = () => parseLocation(entries[index], currentState);
339
347
  return createHistory({
340
348
  getLocation,
341
349
  subscriber: false,
342
350
  pushState: (path, state) => {
343
- currentState = {
344
- ...state,
345
- key: createRandomKey()
346
- };
351
+ currentState = state;
347
352
  entries.push(path);
348
353
  index++;
349
354
  },
350
355
  replaceState: (path, state) => {
351
- currentState = {
352
- ...state,
353
- key: createRandomKey()
354
- };
356
+ currentState = state;
355
357
  entries[index] = path;
356
358
  },
357
359
  back: () => {
@@ -372,7 +374,7 @@
372
374
  pathname: href.substring(0, hashIndex > 0 ? searchIndex > 0 ? Math.min(hashIndex, searchIndex) : hashIndex : searchIndex > 0 ? searchIndex : href.length),
373
375
  hash: hashIndex > -1 ? href.substring(hashIndex) : '',
374
376
  search: searchIndex > -1 ? href.slice(searchIndex, hashIndex === -1 ? undefined : hashIndex) : '',
375
- state
377
+ state: state || {}
376
378
  };
377
379
  }
378
380
 
@@ -381,6 +383,21 @@
381
383
  return (Math.random() + 1).toString(36).substring(7);
382
384
  }
383
385
 
386
+ // type Compute<T> = { [K in keyof T]: T[K] } | never
387
+
388
+ // type AllKeys<T> = T extends any ? keyof T : never
389
+
390
+ // export type MergeUnion<T, Keys extends keyof T = keyof T> = Compute<
391
+ // {
392
+ // [K in Keys]: T[Keys]
393
+ // } & {
394
+ // [K in AllKeys<T>]?: T extends any
395
+ // ? K extends keyof T
396
+ // ? T[K]
397
+ // : never
398
+ // : never
399
+ // }
400
+ // >
384
401
  function last(arr) {
385
402
  return arr[arr.length - 1];
386
403
  }
@@ -790,6 +807,9 @@
790
807
  super(options);
791
808
  }
792
809
  }
810
+ function createRouteMask(opts) {
811
+ return opts;
812
+ }
793
813
 
794
814
  class FileRoute {
795
815
  constructor(path) {
@@ -873,6 +893,9 @@
873
893
  class Router {
874
894
  #unsubHistory;
875
895
  resetNextScroll = false;
896
+ tempLocationKey = `${Math.round(Math.random() * 10000000)}`;
897
+ // nextTemporaryLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
898
+
876
899
  constructor(options) {
877
900
  this.options = {
878
901
  defaultPreloadDelay: 50,
@@ -921,15 +944,15 @@
921
944
  });
922
945
  this.state = this.__store.state;
923
946
  this.update(options);
924
- const next = this.buildNext({
925
- hash: true,
926
- fromCurrent: true,
947
+ const nextLocation = this.buildLocation({
927
948
  search: true,
949
+ params: true,
950
+ hash: true,
928
951
  state: true
929
952
  });
930
- if (this.state.location.href !== next.href) {
953
+ if (this.state.location.href !== nextLocation.href) {
931
954
  this.#commitLocation({
932
- ...next,
955
+ ...nextLocation,
933
956
  replace: true
934
957
  });
935
958
  }
@@ -972,8 +995,10 @@
972
995
  };
973
996
  };
974
997
  #onFocus = () => {
975
- if (this.options.refetchOnWindowFocus ?? true) {
976
- this.invalidate();
998
+ if (this.options.reloadOnWindowFocus ?? true) {
999
+ this.invalidate({
1000
+ __fromFocus: true
1001
+ });
977
1002
  }
978
1003
  };
979
1004
  update = opts => {
@@ -1012,14 +1037,6 @@
1012
1037
  }
1013
1038
  return this;
1014
1039
  };
1015
- buildNext = opts => {
1016
- const next = this.#buildLocation(opts);
1017
- const __matches = this.matchRoutes(next.pathname, next.search);
1018
- return this.#buildLocation({
1019
- ...opts,
1020
- __matches
1021
- });
1022
- };
1023
1040
  cancelMatches = () => {
1024
1041
  this.state.matches.forEach(match => {
1025
1042
  this.cancelMatch(match.id);
@@ -1078,7 +1095,7 @@
1078
1095
  try {
1079
1096
  // Load the matches
1080
1097
  try {
1081
- await this.loadMatches(pendingMatches);
1098
+ await this.loadMatches(pendingMatches.map(d => d.id));
1082
1099
  } catch (err) {
1083
1100
  // swallow this error, since we'll display the
1084
1101
  // errors on the route components
@@ -1127,10 +1144,19 @@
1127
1144
  return this.latestLoadPromise;
1128
1145
  };
1129
1146
  #mergeMatches = (prevMatchesById, nextMatches) => {
1130
- return {
1131
- ...prevMatchesById,
1132
- ...Object.fromEntries(nextMatches.map(match => [match.id, match]))
1147
+ let matchesById = {
1148
+ ...prevMatchesById
1133
1149
  };
1150
+ nextMatches.forEach(match => {
1151
+ if (!matchesById[match.id]) {
1152
+ matchesById[match.id] = match;
1153
+ }
1154
+ matchesById[match.id] = {
1155
+ ...matchesById[match.id],
1156
+ ...match
1157
+ };
1158
+ });
1159
+ return matchesById;
1134
1160
  };
1135
1161
  getRoute = id => {
1136
1162
  const route = this.routesById[id];
@@ -1138,7 +1164,7 @@
1138
1164
  return route;
1139
1165
  };
1140
1166
  preloadRoute = async (navigateOpts = this.state.location) => {
1141
- const next = this.buildNext(navigateOpts);
1167
+ let next = this.buildLocation(navigateOpts);
1142
1168
  const matches = this.matchRoutes(next.pathname, next.search, {
1143
1169
  throwOnError: true
1144
1170
  });
@@ -1148,11 +1174,11 @@
1148
1174
  matchesById: this.#mergeMatches(s.matchesById, matches)
1149
1175
  };
1150
1176
  });
1151
- await this.loadMatches(matches, {
1177
+ await this.loadMatches(matches.map(d => d.id), {
1152
1178
  preload: true,
1153
1179
  maxAge: navigateOpts.maxAge
1154
1180
  });
1155
- return matches;
1181
+ return [last(matches), matches];
1156
1182
  };
1157
1183
  cleanMatches = () => {
1158
1184
  const now = Date.now();
@@ -1306,8 +1332,8 @@
1306
1332
  });
1307
1333
  return matches;
1308
1334
  };
1309
- loadMatches = async (_resolvedMatches, opts) => {
1310
- const getFreshMatches = () => _resolvedMatches.map(d => this.getRouteMatch(d.id));
1335
+ loadMatches = async (matchIds, opts) => {
1336
+ const getFreshMatches = () => matchIds.map(d => this.getRouteMatch(d));
1311
1337
  if (!opts?.preload) {
1312
1338
  getFreshMatches().forEach(match => {
1313
1339
  // Update each match with its latest route data
@@ -1387,8 +1413,9 @@
1387
1413
  }
1388
1414
  }
1389
1415
  } catch (err) {
1390
- if (!opts?.preload) {
1391
- this.navigate(err);
1416
+ if (isRedirect(err)) {
1417
+ if (!opts?.preload) this.navigate(err);
1418
+ return;
1392
1419
  }
1393
1420
  throw err;
1394
1421
  }
@@ -1497,13 +1524,13 @@
1497
1524
  isExternal = true;
1498
1525
  } catch (e) {}
1499
1526
  invariant(!isExternal, 'Attempting to navigate to external url with this.navigate!');
1500
- return this.#commitLocation({
1527
+ return this.#buildAndCommitLocation({
1501
1528
  from: fromString,
1502
1529
  to: toString,
1503
1530
  search,
1504
1531
  hash,
1505
- replace,
1506
1532
  params,
1533
+ replace,
1507
1534
  resetScroll
1508
1535
  });
1509
1536
  };
@@ -1512,7 +1539,7 @@
1512
1539
  ...location,
1513
1540
  to: location.to ? this.resolvePath(location.from ?? '', location.to) : undefined
1514
1541
  };
1515
- const next = this.buildNext(location);
1542
+ const next = this.buildLocation(location);
1516
1543
  if (opts?.pending && this.state.status !== 'pending') {
1517
1544
  return false;
1518
1545
  }
@@ -1545,6 +1572,7 @@
1545
1572
  preloadDelay: userPreloadDelay,
1546
1573
  disabled,
1547
1574
  state,
1575
+ mask,
1548
1576
  resetScroll
1549
1577
  }) => {
1550
1578
  // If this link simply reloads the current route,
@@ -1568,9 +1596,10 @@
1568
1596
  hash,
1569
1597
  replace,
1570
1598
  state,
1599
+ mask,
1571
1600
  resetScroll
1572
1601
  };
1573
- const next = this.buildNext(nextOpts);
1602
+ const next = this.buildLocation(nextOpts);
1574
1603
  preload = preload ?? this.options.defaultPreload;
1575
1604
  const preloadDelay = userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0;
1576
1605
 
@@ -1592,7 +1621,11 @@
1592
1621
  e.preventDefault();
1593
1622
 
1594
1623
  // All is well? Navigate!
1595
- this.#commitLocation(nextOpts);
1624
+ this.#commitLocation({
1625
+ ...next,
1626
+ replace,
1627
+ resetScroll
1628
+ });
1596
1629
  }
1597
1630
  };
1598
1631
 
@@ -1805,84 +1838,180 @@
1805
1838
  });
1806
1839
  };
1807
1840
  #parseLocation = previousLocation => {
1808
- let {
1841
+ const parse = ({
1809
1842
  pathname,
1810
1843
  search,
1811
1844
  hash,
1812
1845
  state
1813
- } = this.history.location;
1814
- const parsedSearch = this.options.parseSearch(search);
1815
- return {
1816
- pathname: pathname,
1817
- searchStr: search,
1818
- search: replaceEqualDeep(previousLocation?.search, parsedSearch),
1819
- hash: hash.split('#').reverse()[0] ?? '',
1820
- href: `${pathname}${search}${hash}`,
1821
- state: state,
1822
- key: state?.key || '__init__'
1846
+ }) => {
1847
+ const parsedSearch = this.options.parseSearch(search);
1848
+ return {
1849
+ pathname: pathname,
1850
+ searchStr: search,
1851
+ search: replaceEqualDeep(previousLocation?.search, parsedSearch),
1852
+ hash: hash.split('#').reverse()[0] ?? '',
1853
+ href: `${pathname}${search}${hash}`,
1854
+ state: replaceEqualDeep(previousLocation?.state, state)
1855
+ };
1823
1856
  };
1857
+ const location = parse(this.history.location);
1858
+ let {
1859
+ __tempLocation,
1860
+ __tempKey
1861
+ } = location.state;
1862
+ if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
1863
+ // Sync up the location keys
1864
+ const parsedTempLocation = parse(__tempLocation);
1865
+ parsedTempLocation.state.key = location.state.key;
1866
+ delete parsedTempLocation.state.__tempLocation;
1867
+ return {
1868
+ ...parsedTempLocation,
1869
+ maskedLocation: location
1870
+ };
1871
+ }
1872
+ return location;
1824
1873
  };
1825
- #buildLocation = (dest = {}) => {
1826
- dest.fromCurrent = dest.fromCurrent ?? dest.to === '';
1827
- const fromPathname = dest.fromCurrent ? this.state.location.pathname : dest.from ?? this.state.location.pathname;
1828
- let pathname = resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? ''}`);
1829
- const fromMatches = this.matchRoutes(this.state.location.pathname, this.state.location.search);
1830
- const prevParams = {
1831
- ...last(fromMatches)?.params
1874
+ buildLocation = (opts = {}) => {
1875
+ const build = (dest = {}, matches) => {
1876
+ const from = this.state.location;
1877
+ const fromPathname = dest.from ?? from.pathname;
1878
+ let pathname = resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? ''}`);
1879
+ const fromMatches = this.matchRoutes(fromPathname, from.search);
1880
+ const prevParams = {
1881
+ ...last(fromMatches)?.params
1882
+ };
1883
+ let nextParams = (dest.params ?? true) === true ? prevParams : functionalUpdate(dest.params, prevParams);
1884
+ if (nextParams) {
1885
+ matches?.map(d => this.getRoute(d.routeId).options.stringifyParams).filter(Boolean).forEach(fn => {
1886
+ nextParams = {
1887
+ ...nextParams,
1888
+ ...fn(nextParams)
1889
+ };
1890
+ });
1891
+ }
1892
+ pathname = interpolatePath(pathname, nextParams ?? {});
1893
+ const preSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
1894
+ const postSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
1895
+
1896
+ // Pre filters first
1897
+ const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), from.search) : from.search;
1898
+
1899
+ // Then the link/navigate function
1900
+ const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
1901
+ : dest.search ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1902
+ : preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
1903
+ : {};
1904
+
1905
+ // Then post filters
1906
+ const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
1907
+ const search = replaceEqualDeep(from.search, postFilteredSearch);
1908
+ const searchStr = this.options.stringifySearch(search);
1909
+ const hash = dest.hash === true ? from.hash : dest.hash ? functionalUpdate(dest.hash, from.hash) : from.hash;
1910
+ const hashStr = hash ? `#${hash}` : '';
1911
+ let nextState = dest.state === true ? from.state : dest.state ? functionalUpdate(dest.state, from.state) : from.state;
1912
+ nextState = replaceEqualDeep(from.state, nextState);
1913
+ return {
1914
+ pathname,
1915
+ search,
1916
+ searchStr,
1917
+ state: nextState,
1918
+ hash,
1919
+ href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
1920
+ unmaskOnReload: dest.unmaskOnReload
1921
+ };
1832
1922
  };
1833
- let nextParams = (dest.params ?? true) === true ? prevParams : functionalUpdate(dest.params, prevParams);
1834
- if (nextParams) {
1835
- dest.__matches?.map(d => this.getRoute(d.routeId).options.stringifyParams).filter(Boolean).forEach(fn => {
1836
- nextParams = {
1837
- ...nextParams,
1838
- ...fn(nextParams)
1839
- };
1923
+ const buildWithMatches = (dest = {}, maskedDest) => {
1924
+ let next = build(dest);
1925
+ let maskedNext = maskedDest ? build(maskedDest) : undefined;
1926
+ if (!maskedNext) {
1927
+ let params = {};
1928
+ let foundMask = this.options.routeMasks?.find(d => {
1929
+ const match = matchPathname(this.basepath, next.pathname, {
1930
+ to: d.from,
1931
+ fuzzy: false
1932
+ });
1933
+ if (match) {
1934
+ params = match;
1935
+ return true;
1936
+ }
1937
+ return false;
1938
+ });
1939
+ if (foundMask) {
1940
+ foundMask = {
1941
+ ...foundMask,
1942
+ from: interpolatePath(foundMask.from, params)
1943
+ };
1944
+ maskedDest = foundMask;
1945
+ maskedNext = build(maskedDest);
1946
+ }
1947
+ }
1948
+ const nextMatches = this.matchRoutes(next.pathname, next.search);
1949
+ const maskedMatches = maskedNext ? this.matchRoutes(maskedNext.pathname, maskedNext.search) : undefined;
1950
+ const maskedFinal = maskedNext ? build(maskedDest, maskedMatches) : undefined;
1951
+ const final = build(dest, nextMatches);
1952
+ if (maskedFinal) {
1953
+ final.maskedLocation = maskedFinal;
1954
+ }
1955
+ return final;
1956
+ };
1957
+ if (opts.mask) {
1958
+ return buildWithMatches(opts, {
1959
+ ...pick(opts, ['from']),
1960
+ ...opts.mask
1840
1961
  });
1841
1962
  }
1842
- pathname = interpolatePath(pathname, nextParams ?? {});
1843
- const preSearchFilters = dest.__matches?.map(match => this.getRoute(match.routeId).options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
1844
- const postSearchFilters = dest.__matches?.map(match => this.getRoute(match.routeId).options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
1845
-
1846
- // Pre filters first
1847
- const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), this.state.location.search) : this.state.location.search;
1848
-
1849
- // Then the link/navigate function
1850
- const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
1851
- : dest.search ? functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1852
- : preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
1853
- : {};
1854
-
1855
- // Then post filters
1856
- const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
1857
- const search = replaceEqualDeep(this.state.location.search, postFilteredSearch);
1858
- const searchStr = this.options.stringifySearch(search);
1859
- const hash = dest.hash === true ? this.state.location.hash : functionalUpdate(dest.hash, this.state.location.hash);
1860
- const hashStr = hash ? `#${hash}` : '';
1861
- const nextState = dest.state === true ? this.state.location.state : functionalUpdate(dest.state, this.state.location.state);
1862
- return {
1863
- pathname,
1864
- search,
1865
- searchStr,
1866
- state: nextState,
1867
- hash,
1868
- href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
1869
- key: dest.key
1870
- };
1963
+ return buildWithMatches(opts);
1871
1964
  };
1872
- #commitLocation = async location => {
1873
- const next = this.buildNext(location);
1965
+ #buildAndCommitLocation = ({
1966
+ replace,
1967
+ resetScroll,
1968
+ ...rest
1969
+ } = {}) => {
1970
+ const location = this.buildLocation(rest);
1971
+ return this.#commitLocation({
1972
+ ...location,
1973
+ replace,
1974
+ resetScroll
1975
+ });
1976
+ };
1977
+ #commitLocation = async next => {
1874
1978
  if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
1875
1979
  let nextAction = 'replace';
1876
- if (!location.replace) {
1980
+ if (!next.replace) {
1877
1981
  nextAction = 'push';
1878
1982
  }
1879
1983
  const isSameUrl = this.state.location.href === next.href;
1880
- if (isSameUrl && !next.key) {
1984
+ if (isSameUrl) {
1881
1985
  nextAction = 'replace';
1882
1986
  }
1883
- const href = `${next.pathname}${next.searchStr}${next.hash ? `#${next.hash}` : ''}`;
1884
- this.history[nextAction === 'push' ? 'push' : 'replace'](href, next.state);
1885
- this.resetNextScroll = location.resetScroll ?? true;
1987
+ let {
1988
+ maskedLocation,
1989
+ ...nextHistory
1990
+ } = next;
1991
+ if (maskedLocation) {
1992
+ nextHistory = {
1993
+ ...maskedLocation,
1994
+ state: {
1995
+ ...maskedLocation.state,
1996
+ __tempKey: undefined,
1997
+ __tempLocation: {
1998
+ ...nextHistory,
1999
+ search: nextHistory.searchStr,
2000
+ state: {
2001
+ ...nextHistory.state,
2002
+ __tempKey: undefined,
2003
+ __tempLocation: undefined,
2004
+ key: undefined
2005
+ }
2006
+ }
2007
+ }
2008
+ };
2009
+ if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
2010
+ nextHistory.state.__tempKey = this.tempLocationKey;
2011
+ }
2012
+ }
2013
+ this.history[nextAction === 'push' ? 'push' : 'replace'](nextHistory.href, nextHistory.state);
2014
+ this.resetNextScroll = next.resetScroll ?? true;
1886
2015
  return this.latestLoadPromise;
1887
2016
  };
1888
2017
  getRouteMatch = id => {
@@ -1931,25 +2060,26 @@
1931
2060
  if (childMatch) {
1932
2061
  return this.invalidate({
1933
2062
  matchId: childMatch.id,
1934
- reload: false
2063
+ reload: false,
2064
+ __fromFocus: opts.__fromFocus
1935
2065
  });
1936
2066
  }
1937
2067
  } else {
1938
2068
  this.__store.batch(() => {
1939
2069
  Object.values(this.state.matchesById).forEach(match => {
1940
- this.setRouteMatch(match.id, s => ({
1941
- ...s,
1942
- invalid: true
1943
- }));
2070
+ const route = this.getRoute(match.routeId);
2071
+ const shouldInvalidate = opts?.__fromFocus ? route.options.reloadOnWindowFocus ?? true : true;
2072
+ if (shouldInvalidate) {
2073
+ this.setRouteMatch(match.id, s => ({
2074
+ ...s,
2075
+ invalid: true
2076
+ }));
2077
+ }
1944
2078
  });
1945
2079
  });
1946
2080
  }
1947
2081
  if (opts?.reload ?? true) {
1948
- return this.navigate({
1949
- fromCurrent: true,
1950
- replace: true,
1951
- search: true
1952
- });
2082
+ return this.load();
1953
2083
  }
1954
2084
  };
1955
2085
  }
@@ -2016,7 +2146,7 @@
2016
2146
  let cache;
2017
2147
  let pathDidChange = false;
2018
2148
  const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage;
2019
- const defaultGetKey = location => location.key;
2149
+ const defaultGetKey = location => location.state.key;
2020
2150
  function watchScrollPositions(router, opts) {
2021
2151
  const getKey = opts?.getKey || defaultGetKey;
2022
2152
  if (sessionsStorage) {
@@ -2252,6 +2382,8 @@
2252
2382
  search,
2253
2383
  params,
2254
2384
  to = '.',
2385
+ state,
2386
+ mask,
2255
2387
  preload,
2256
2388
  preloadDelay,
2257
2389
  replace,
@@ -2307,7 +2439,7 @@
2307
2439
  ...resolvedActiveProps,
2308
2440
  ...resolvedInactiveProps,
2309
2441
  ...rest,
2310
- href: disabled ? undefined : next.href,
2442
+ href: disabled ? undefined : next.maskedLocation ? next.maskedLocation.href : next.href,
2311
2443
  onClick: composeHandlers([onClick, handleReactClick]),
2312
2444
  onFocus: composeHandlers([onFocus, handleFocus]),
2313
2445
  onMouseEnter: composeHandlers([onMouseEnter, handleEnter]),
@@ -2374,11 +2506,25 @@
2374
2506
  return state.renderedMatchIds;
2375
2507
  }
2376
2508
  });
2509
+ const locationKey = useRouterState({
2510
+ select: d => d.resolvedLocation.state?.key
2511
+ });
2512
+ const route = router.getRoute(rootRouteId);
2513
+ const errorComponent = React__namespace.useCallback(props => {
2514
+ return /*#__PURE__*/React__namespace.createElement(ErrorComponent, {
2515
+ ...props,
2516
+ useMatch: route.useMatch,
2517
+ useContext: route.useContext,
2518
+ useRouteContext: route.useRouteContext,
2519
+ useSearch: route.useSearch,
2520
+ useParams: route.useParams
2521
+ });
2522
+ }, [route]);
2377
2523
  return /*#__PURE__*/React__namespace.createElement(matchIdsContext.Provider, {
2378
2524
  value: [undefined, ...matchIds]
2379
2525
  }, /*#__PURE__*/React__namespace.createElement(CatchBoundary, {
2380
- errorComponent: ErrorComponent,
2381
- route: router.getRoute(rootRouteId),
2526
+ resetKey: locationKey,
2527
+ errorComponent: errorComponent,
2382
2528
  onCatch: () => {
2383
2529
  warning(false, `Error in router! Consider setting an 'errorComponent' in your RootRoute! 👍`);
2384
2530
  }
@@ -2502,10 +2648,23 @@
2502
2648
  const matchId = matchIds[0];
2503
2649
  const routeId = router.getRouteMatch(matchId).routeId;
2504
2650
  const route = router.getRoute(routeId);
2651
+ const locationKey = useRouterState({
2652
+ select: s => s.resolvedLocation.state?.key
2653
+ });
2505
2654
  const PendingComponent = route.options.pendingComponent ?? router.options.defaultPendingComponent ?? defaultPending;
2506
- const errorComponent = route.options.errorComponent ?? router.options.defaultErrorComponent ?? ErrorComponent;
2655
+ const routeErrorComponent = route.options.errorComponent ?? router.options.defaultErrorComponent ?? ErrorComponent;
2507
2656
  const ResolvedSuspenseBoundary = route.options.wrapInSuspense ?? !route.isRoot ? React__namespace.Suspense : SafeFragment;
2508
- const ResolvedCatchBoundary = !!errorComponent ? CatchBoundary : SafeFragment;
2657
+ const ResolvedCatchBoundary = !!routeErrorComponent ? CatchBoundary : SafeFragment;
2658
+ const errorComponent = React__namespace.useCallback(props => {
2659
+ return /*#__PURE__*/React__namespace.createElement(routeErrorComponent, {
2660
+ ...props,
2661
+ useMatch: route.useMatch,
2662
+ useContext: route.useContext,
2663
+ useRouteContext: route.useRouteContext,
2664
+ useSearch: route.useSearch,
2665
+ useParams: route.useParams
2666
+ });
2667
+ }, [route]);
2509
2668
  return /*#__PURE__*/React__namespace.createElement(matchIdsContext.Provider, {
2510
2669
  value: matchIds
2511
2670
  }, /*#__PURE__*/React__namespace.createElement(ResolvedSuspenseBoundary, {
@@ -2517,9 +2676,8 @@
2517
2676
  useParams: route.useParams
2518
2677
  })
2519
2678
  }, /*#__PURE__*/React__namespace.createElement(ResolvedCatchBoundary, {
2520
- key: route.id,
2679
+ resetKey: locationKey,
2521
2680
  errorComponent: errorComponent,
2522
- route: route,
2523
2681
  onCatch: () => {
2524
2682
  warning(false, `Error in route match: ${matchId}`);
2525
2683
  }
@@ -2595,57 +2753,45 @@
2595
2753
  // there has to be a better way to reset error boundaries when the
2596
2754
  // router's location key changes.
2597
2755
 
2598
- class CatchBoundary extends React__namespace.Component {
2599
- state = {
2600
- error: false,
2601
- info: undefined
2602
- };
2603
- componentDidCatch(error, info) {
2604
- this.props.onCatch(error, info);
2605
- this.setState({
2606
- error,
2607
- info
2608
- });
2609
- }
2610
- render() {
2611
- return /*#__PURE__*/React__namespace.createElement(CatchBoundaryInner, _extends({}, this.props, {
2612
- errorState: this.state,
2613
- reset: () => this.setState({})
2614
- }));
2615
- }
2616
- }
2617
- function CatchBoundaryInner(props) {
2618
- const locationKey = useRouterState({
2619
- select: d => d.resolvedLocation.key
2620
- });
2621
- const [activeErrorState, setActiveErrorState] = React__namespace.useState(props.errorState);
2756
+ function CatchBoundary(props) {
2622
2757
  const errorComponent = props.errorComponent ?? ErrorComponent;
2623
- const prevKeyRef = React__namespace.useRef('');
2624
- React__namespace.useEffect(() => {
2625
- if (activeErrorState) {
2626
- if (locationKey !== prevKeyRef.current) {
2627
- setActiveErrorState({});
2758
+ return /*#__PURE__*/React__namespace.createElement(CatchBoundaryImpl, {
2759
+ resetKey: props.resetKey,
2760
+ onCatch: props.onCatch,
2761
+ children: ({
2762
+ error
2763
+ }) => {
2764
+ if (error) {
2765
+ return /*#__PURE__*/React__namespace.createElement(errorComponent, {
2766
+ error
2767
+ });
2628
2768
  }
2769
+ return props.children;
2629
2770
  }
2630
- prevKeyRef.current = locationKey;
2631
- }, [activeErrorState, locationKey]);
2632
- React__namespace.useEffect(() => {
2633
- if (props.errorState.error) {
2634
- setActiveErrorState(props.errorState);
2635
- }
2636
- // props.reset()
2637
- }, [props.errorState.error]);
2638
- if (props.errorState.error && activeErrorState.error) {
2639
- return /*#__PURE__*/React__namespace.createElement(errorComponent, {
2640
- ...activeErrorState,
2641
- useMatch: props.route.useMatch,
2642
- useContext: props.route.useContext,
2643
- useRouteContext: props.route.useRouteContext,
2644
- useSearch: props.route.useSearch,
2645
- useParams: props.route.useParams
2646
- });
2771
+ });
2772
+ }
2773
+ class CatchBoundaryImpl extends React__namespace.Component {
2774
+ state = {
2775
+ error: null
2776
+ };
2777
+ static getDerivedStateFromError(error) {
2778
+ return {
2779
+ error
2780
+ };
2781
+ }
2782
+ componentDidUpdate(prevProps, prevState) {
2783
+ if (prevState.error && prevProps.resetKey !== this.props.resetKey) {
2784
+ this.setState({
2785
+ error: null
2786
+ });
2787
+ }
2788
+ }
2789
+ componentDidCatch(error) {
2790
+ this.props.onCatch?.(error);
2791
+ }
2792
+ render() {
2793
+ return this.props.children(this.state);
2647
2794
  }
2648
- return props.children;
2649
2795
  }
2650
2796
  function ErrorComponent({
2651
2797
  error
@@ -2773,6 +2919,8 @@
2773
2919
 
2774
2920
  exports.Await = Await;
2775
2921
  exports.Block = Block;
2922
+ exports.CatchBoundary = CatchBoundary;
2923
+ exports.CatchBoundaryImpl = CatchBoundaryImpl;
2776
2924
  exports.ErrorComponent = ErrorComponent;
2777
2925
  exports.FileRoute = FileRoute;
2778
2926
  exports.Link = Link;
@@ -2792,6 +2940,7 @@
2792
2940
  exports.createBrowserHistory = createBrowserHistory;
2793
2941
  exports.createHashHistory = createHashHistory;
2794
2942
  exports.createMemoryHistory = createMemoryHistory;
2943
+ exports.createRouteMask = createRouteMask;
2795
2944
  exports.decode = decode;
2796
2945
  exports.defaultParseSearch = defaultParseSearch;
2797
2946
  exports.defaultStringifySearch = defaultStringifySearch;