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

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,174 @@
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(from.pathname, 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
+ const foundMask = this.options.routeMasks?.find(d => {
1928
+ const match = matchPathname(this.basepath, next.pathname, {
1929
+ to: d.from,
1930
+ fuzzy: false
1931
+ });
1932
+ if (match) {
1933
+ return match;
1934
+ }
1935
+ return false;
1936
+ });
1937
+ if (foundMask) {
1938
+ maskedDest = foundMask;
1939
+ maskedNext = build(maskedDest);
1940
+ }
1941
+ }
1942
+ const nextMatches = this.matchRoutes(next.pathname, next.search);
1943
+ const maskedMatches = maskedNext ? this.matchRoutes(maskedNext.pathname, maskedNext.search) : undefined;
1944
+ const maskedFinal = maskedNext ? build(maskedDest, maskedMatches) : undefined;
1945
+ const final = build(dest, nextMatches);
1946
+ if (maskedFinal) {
1947
+ final.maskedLocation = maskedFinal;
1948
+ }
1949
+ return final;
1950
+ };
1951
+ if (opts.mask) {
1952
+ return buildWithMatches(opts, {
1953
+ ...pick(opts, ['from']),
1954
+ ...opts.mask
1840
1955
  });
1841
1956
  }
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
- };
1957
+ return buildWithMatches(opts);
1871
1958
  };
1872
- #commitLocation = async location => {
1873
- const next = this.buildNext(location);
1959
+ #buildAndCommitLocation = ({
1960
+ replace,
1961
+ resetScroll,
1962
+ ...rest
1963
+ } = {}) => {
1964
+ const location = this.buildLocation(rest);
1965
+ return this.#commitLocation({
1966
+ ...location,
1967
+ replace,
1968
+ resetScroll
1969
+ });
1970
+ };
1971
+ #commitLocation = async next => {
1874
1972
  if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
1875
1973
  let nextAction = 'replace';
1876
- if (!location.replace) {
1974
+ if (!next.replace) {
1877
1975
  nextAction = 'push';
1878
1976
  }
1879
1977
  const isSameUrl = this.state.location.href === next.href;
1880
- if (isSameUrl && !next.key) {
1978
+ if (isSameUrl) {
1881
1979
  nextAction = 'replace';
1882
1980
  }
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;
1981
+ let {
1982
+ maskedLocation,
1983
+ ...nextHistory
1984
+ } = next;
1985
+ if (maskedLocation) {
1986
+ nextHistory = {
1987
+ ...maskedLocation,
1988
+ state: {
1989
+ ...maskedLocation.state,
1990
+ __tempKey: undefined,
1991
+ __tempLocation: {
1992
+ ...nextHistory,
1993
+ search: nextHistory.searchStr,
1994
+ state: {
1995
+ ...nextHistory.state,
1996
+ __tempKey: undefined,
1997
+ __tempLocation: undefined,
1998
+ key: undefined
1999
+ }
2000
+ }
2001
+ }
2002
+ };
2003
+ if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
2004
+ nextHistory.state.__tempKey = this.tempLocationKey;
2005
+ }
2006
+ }
2007
+ this.history[nextAction === 'push' ? 'push' : 'replace'](nextHistory.href, nextHistory.state);
2008
+ this.resetNextScroll = next.resetScroll ?? true;
1886
2009
  return this.latestLoadPromise;
1887
2010
  };
1888
2011
  getRouteMatch = id => {
@@ -1931,25 +2054,26 @@
1931
2054
  if (childMatch) {
1932
2055
  return this.invalidate({
1933
2056
  matchId: childMatch.id,
1934
- reload: false
2057
+ reload: false,
2058
+ __fromFocus: opts.__fromFocus
1935
2059
  });
1936
2060
  }
1937
2061
  } else {
1938
2062
  this.__store.batch(() => {
1939
2063
  Object.values(this.state.matchesById).forEach(match => {
1940
- this.setRouteMatch(match.id, s => ({
1941
- ...s,
1942
- invalid: true
1943
- }));
2064
+ const route = this.getRoute(match.routeId);
2065
+ const shouldInvalidate = opts?.__fromFocus ? route.options.reloadOnWindowFocus ?? true : true;
2066
+ if (shouldInvalidate) {
2067
+ this.setRouteMatch(match.id, s => ({
2068
+ ...s,
2069
+ invalid: true
2070
+ }));
2071
+ }
1944
2072
  });
1945
2073
  });
1946
2074
  }
1947
2075
  if (opts?.reload ?? true) {
1948
- return this.navigate({
1949
- fromCurrent: true,
1950
- replace: true,
1951
- search: true
1952
- });
2076
+ return this.load();
1953
2077
  }
1954
2078
  };
1955
2079
  }
@@ -2016,7 +2140,7 @@
2016
2140
  let cache;
2017
2141
  let pathDidChange = false;
2018
2142
  const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage;
2019
- const defaultGetKey = location => location.key;
2143
+ const defaultGetKey = location => location.state.key;
2020
2144
  function watchScrollPositions(router, opts) {
2021
2145
  const getKey = opts?.getKey || defaultGetKey;
2022
2146
  if (sessionsStorage) {
@@ -2252,6 +2376,8 @@
2252
2376
  search,
2253
2377
  params,
2254
2378
  to = '.',
2379
+ state,
2380
+ mask,
2255
2381
  preload,
2256
2382
  preloadDelay,
2257
2383
  replace,
@@ -2307,7 +2433,7 @@
2307
2433
  ...resolvedActiveProps,
2308
2434
  ...resolvedInactiveProps,
2309
2435
  ...rest,
2310
- href: disabled ? undefined : next.href,
2436
+ href: disabled ? undefined : next.maskedLocation ? next.maskedLocation.href : next.href,
2311
2437
  onClick: composeHandlers([onClick, handleReactClick]),
2312
2438
  onFocus: composeHandlers([onFocus, handleFocus]),
2313
2439
  onMouseEnter: composeHandlers([onMouseEnter, handleEnter]),
@@ -2374,11 +2500,25 @@
2374
2500
  return state.renderedMatchIds;
2375
2501
  }
2376
2502
  });
2503
+ const locationKey = useRouterState({
2504
+ select: d => d.resolvedLocation.state?.key
2505
+ });
2506
+ const route = router.getRoute(rootRouteId);
2507
+ const errorComponent = React__namespace.useCallback(props => {
2508
+ return /*#__PURE__*/React__namespace.createElement(ErrorComponent, {
2509
+ ...props,
2510
+ useMatch: route.useMatch,
2511
+ useContext: route.useContext,
2512
+ useRouteContext: route.useRouteContext,
2513
+ useSearch: route.useSearch,
2514
+ useParams: route.useParams
2515
+ });
2516
+ }, [route]);
2377
2517
  return /*#__PURE__*/React__namespace.createElement(matchIdsContext.Provider, {
2378
2518
  value: [undefined, ...matchIds]
2379
2519
  }, /*#__PURE__*/React__namespace.createElement(CatchBoundary, {
2380
- errorComponent: ErrorComponent,
2381
- route: router.getRoute(rootRouteId),
2520
+ resetKey: locationKey,
2521
+ errorComponent: errorComponent,
2382
2522
  onCatch: () => {
2383
2523
  warning(false, `Error in router! Consider setting an 'errorComponent' in your RootRoute! 👍`);
2384
2524
  }
@@ -2502,10 +2642,23 @@
2502
2642
  const matchId = matchIds[0];
2503
2643
  const routeId = router.getRouteMatch(matchId).routeId;
2504
2644
  const route = router.getRoute(routeId);
2645
+ const locationKey = useRouterState({
2646
+ select: s => s.resolvedLocation.state?.key
2647
+ });
2505
2648
  const PendingComponent = route.options.pendingComponent ?? router.options.defaultPendingComponent ?? defaultPending;
2506
- const errorComponent = route.options.errorComponent ?? router.options.defaultErrorComponent ?? ErrorComponent;
2649
+ const routeErrorComponent = route.options.errorComponent ?? router.options.defaultErrorComponent ?? ErrorComponent;
2507
2650
  const ResolvedSuspenseBoundary = route.options.wrapInSuspense ?? !route.isRoot ? React__namespace.Suspense : SafeFragment;
2508
- const ResolvedCatchBoundary = !!errorComponent ? CatchBoundary : SafeFragment;
2651
+ const ResolvedCatchBoundary = !!routeErrorComponent ? CatchBoundary : SafeFragment;
2652
+ const errorComponent = React__namespace.useCallback(props => {
2653
+ return /*#__PURE__*/React__namespace.createElement(routeErrorComponent, {
2654
+ ...props,
2655
+ useMatch: route.useMatch,
2656
+ useContext: route.useContext,
2657
+ useRouteContext: route.useRouteContext,
2658
+ useSearch: route.useSearch,
2659
+ useParams: route.useParams
2660
+ });
2661
+ }, [route]);
2509
2662
  return /*#__PURE__*/React__namespace.createElement(matchIdsContext.Provider, {
2510
2663
  value: matchIds
2511
2664
  }, /*#__PURE__*/React__namespace.createElement(ResolvedSuspenseBoundary, {
@@ -2517,9 +2670,8 @@
2517
2670
  useParams: route.useParams
2518
2671
  })
2519
2672
  }, /*#__PURE__*/React__namespace.createElement(ResolvedCatchBoundary, {
2520
- key: route.id,
2673
+ resetKey: locationKey,
2521
2674
  errorComponent: errorComponent,
2522
- route: route,
2523
2675
  onCatch: () => {
2524
2676
  warning(false, `Error in route match: ${matchId}`);
2525
2677
  }
@@ -2595,57 +2747,45 @@
2595
2747
  // there has to be a better way to reset error boundaries when the
2596
2748
  // router's location key changes.
2597
2749
 
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);
2750
+ function CatchBoundary(props) {
2622
2751
  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({});
2752
+ return /*#__PURE__*/React__namespace.createElement(CatchBoundaryImpl, {
2753
+ resetKey: props.resetKey,
2754
+ onCatch: props.onCatch,
2755
+ children: ({
2756
+ error
2757
+ }) => {
2758
+ if (error) {
2759
+ return /*#__PURE__*/React__namespace.createElement(errorComponent, {
2760
+ error
2761
+ });
2628
2762
  }
2763
+ return props.children;
2629
2764
  }
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
- });
2765
+ });
2766
+ }
2767
+ class CatchBoundaryImpl extends React__namespace.Component {
2768
+ state = {
2769
+ error: null
2770
+ };
2771
+ static getDerivedStateFromError(error) {
2772
+ return {
2773
+ error
2774
+ };
2775
+ }
2776
+ componentDidUpdate(prevProps, prevState) {
2777
+ if (prevState.error && prevProps.resetKey !== this.props.resetKey) {
2778
+ this.setState({
2779
+ error: null
2780
+ });
2781
+ }
2782
+ }
2783
+ componentDidCatch(error) {
2784
+ this.props.onCatch?.(error);
2785
+ }
2786
+ render() {
2787
+ return this.props.children(this.state);
2647
2788
  }
2648
- return props.children;
2649
2789
  }
2650
2790
  function ErrorComponent({
2651
2791
  error
@@ -2773,6 +2913,8 @@
2773
2913
 
2774
2914
  exports.Await = Await;
2775
2915
  exports.Block = Block;
2916
+ exports.CatchBoundary = CatchBoundary;
2917
+ exports.CatchBoundaryImpl = CatchBoundaryImpl;
2776
2918
  exports.ErrorComponent = ErrorComponent;
2777
2919
  exports.FileRoute = FileRoute;
2778
2920
  exports.Link = Link;
@@ -2792,6 +2934,7 @@
2792
2934
  exports.createBrowserHistory = createBrowserHistory;
2793
2935
  exports.createHashHistory = createHashHistory;
2794
2936
  exports.createMemoryHistory = createMemoryHistory;
2937
+ exports.createRouteMask = createRouteMask;
2795
2938
  exports.decode = decode;
2796
2939
  exports.defaultParseSearch = defaultParseSearch;
2797
2940
  exports.defaultStringifySearch = defaultStringifySearch;