@tanstack/react-router 0.0.1-beta.225 → 0.0.1-beta.227

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.
@@ -637,7 +637,6 @@ function Matches() {
637
637
  matches: matches
638
638
  }) : null));
639
639
  }
640
- const defaultPending = () => null;
641
640
  function SafeFragment(props) {
642
641
  return /*#__PURE__*/React.createElement(React.Fragment, null, props.children);
643
642
  }
@@ -652,9 +651,9 @@ function Match({
652
651
  const routeId = match?.routeId;
653
652
  const route = routesById[routeId];
654
653
  const locationKey = useRouterState().location.state?.key;
655
- const PendingComponent = route.options.pendingComponent ?? options.defaultPendingComponent ?? defaultPending;
654
+ const PendingComponent = route.options.pendingComponent ?? options.defaultPendingComponent;
656
655
  const routeErrorComponent = route.options.errorComponent ?? options.defaultErrorComponent ?? ErrorComponent;
657
- const ResolvedSuspenseBoundary = route.options.wrapInSuspense ? React.Suspense : SafeFragment;
656
+ const ResolvedSuspenseBoundary = route.options.wrapInSuspense ?? PendingComponent ? React.Suspense : SafeFragment;
658
657
  const errorComponent = routeErrorComponent ? React.useCallback(props => {
659
658
  return /*#__PURE__*/React.createElement(routeErrorComponent, {
660
659
  ...props,
@@ -664,6 +663,7 @@ function Match({
664
663
  useParams: route.useParams
665
664
  });
666
665
  }, [route]) : undefined;
666
+ const ResolvedCatchBoundary = errorComponent ? CatchBoundary : SafeFragment;
667
667
  return /*#__PURE__*/React.createElement(matchesContext.Provider, {
668
668
  value: matches
669
669
  }, /*#__PURE__*/React.createElement(ResolvedSuspenseBoundary, {
@@ -673,7 +673,7 @@ function Match({
673
673
  useSearch: route.useSearch,
674
674
  useParams: route.useParams
675
675
  })
676
- }, errorComponent ? /*#__PURE__*/React.createElement(CatchBoundary, {
676
+ }, /*#__PURE__*/React.createElement(ResolvedCatchBoundary, {
677
677
  resetKey: locationKey,
678
678
  errorComponent: errorComponent,
679
679
  onCatch: () => {
@@ -681,8 +681,6 @@ function Match({
681
681
  }
682
682
  }, /*#__PURE__*/React.createElement(MatchInner, {
683
683
  match: match
684
- })) : /*#__PURE__*/React.createElement(SafeFragment, null, /*#__PURE__*/React.createElement(MatchInner, {
685
- match: match
686
684
  }))));
687
685
  }
688
686
  function MatchInner({
@@ -693,6 +691,9 @@ function MatchInner({
693
691
  routesById
694
692
  } = useRouter();
695
693
  const route = routesById[match.routeId];
694
+ if (match.id.split('/').length === 4) {
695
+ console.log(match.id, pick(match, ['status', 'cause', 'isFetching']));
696
+ }
696
697
  if (match.status === 'error') {
697
698
  throw match.error;
698
699
  }
@@ -786,31 +787,6 @@ function useLoaderData(opts) {
786
787
  return typeof opts.select === 'function' ? opts.select(match?.loaderData) : match?.loaderData;
787
788
  }
788
789
 
789
- // export type RouterContext<
790
- // TRouteTree extends AnyRoute,
791
- // // TDehydrated extends Record<string, any>,
792
- // > = {
793
- // buildLink: BuildLinkFn<TRouteTree>
794
- // state: RouterState<TRouteTree>
795
- // navigate: NavigateFn<TRouteTree>
796
- // matchRoute: MatchRouteFn<TRouteTree>
797
- // routeTree: TRouteTree
798
- // routesById: RoutesById<TRouteTree>
799
- // options: RouterOptions<TRouteTree>
800
- // history: RouterHistory
801
- // load: LoadFn
802
- // buildLocation: BuildLocationFn<TRouteTree>
803
- // subscribe: Router<TRouteTree>['subscribe']
804
- // resetNextScrollRef: React.MutableRefObject<boolean>
805
- // injectedHtmlRef: React.MutableRefObject<InjectedHtmlEntry[]>
806
- // injectHtml: (entry: InjectedHtmlEntry) => void
807
- // dehydrateData: <T>(
808
- // key: any,
809
- // getData: T | (() => Promise<T> | T),
810
- // ) => () => void
811
- // hydrateData: <T>(key: any) => T | undefined
812
- // }
813
-
814
790
  const routerContext = /*#__PURE__*/React.createContext(null);
815
791
  if (typeof document !== 'undefined') {
816
792
  window.__TSR_ROUTER_CONTEXT__ = routerContext;
@@ -879,7 +855,7 @@ function RouterProvider({
879
855
  return () => {
880
856
  unsub();
881
857
  };
882
- }, [history]);
858
+ }, [router.history]);
883
859
  React.useLayoutEffect(() => {
884
860
  if (!isTransitioning && state.resolvedLocation !== state.location) {
885
861
  router.emit({
@@ -1484,38 +1460,66 @@ class Router {
1484
1460
  }
1485
1461
  return;
1486
1462
  });
1487
- const matches = matchedRoutes.map((route, index) => {
1463
+ const matches = [];
1464
+ matchedRoutes.forEach((route, index) => {
1465
+ // Take each matched route and resolve + validate its search params
1466
+ // This has to happen serially because each route's search params
1467
+ // can depend on the parent route's search params
1468
+ // It must also happen before we create the match so that we can
1469
+ // pass the search params to the route's potential key function
1470
+ // which is used to uniquely identify the route match in state
1471
+
1472
+ const parentMatch = matches[index - 1];
1473
+ const [preMatchSearch, searchError] = (() => {
1474
+ // Validate the search params and stabilize them
1475
+ const parentSearch = parentMatch?.search ?? locationSearch;
1476
+ try {
1477
+ const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
1478
+ let search = validator?.(parentSearch) ?? {};
1479
+ return [{
1480
+ ...parentSearch,
1481
+ ...search
1482
+ }, undefined];
1483
+ } catch (err) {
1484
+ const searchError = new SearchParamError(err.message, {
1485
+ cause: err
1486
+ });
1487
+ if (opts?.throwOnError) {
1488
+ throw searchError;
1489
+ }
1490
+ return [parentSearch, searchError];
1491
+ }
1492
+ })();
1488
1493
  const interpolatedPath = interpolatePath(route.path, routeParams);
1489
- const matchId = interpolatePath(route.id, routeParams, true);
1494
+ const matchId = interpolatePath(route.id, routeParams, true) + (route.options.key?.({
1495
+ search: preMatchSearch,
1496
+ location: this.state.location
1497
+ }) ?? '');
1490
1498
 
1491
1499
  // Waste not, want not. If we already have a match for this route,
1492
1500
  // reuse it. This is important for layout routes, which might stick
1493
1501
  // around between navigation actions that only change leaf routes.
1494
1502
  const existingMatch = getRouteMatch(this.state, matchId);
1495
1503
  const cause = this.state.matches.find(d => d.id === matchId) ? 'stay' : 'enter';
1496
- if (existingMatch) {
1497
- return {
1498
- ...existingMatch,
1499
- cause
1500
- };
1501
- }
1502
1504
 
1503
1505
  // Create a fresh route match
1504
1506
  const hasLoaders = !!(route.options.loader || componentTypes.some(d => route.options[d]?.preload));
1505
- const routeMatch = {
1507
+ const match = existingMatch ? {
1508
+ ...existingMatch,
1509
+ cause
1510
+ } : {
1506
1511
  id: matchId,
1507
1512
  routeId: route.id,
1508
1513
  params: routeParams,
1509
1514
  pathname: joinPaths([this.basepath, interpolatedPath]),
1510
1515
  updatedAt: Date.now(),
1511
- routeSearch: {},
1512
1516
  search: {},
1517
+ searchError: undefined,
1513
1518
  status: hasLoaders ? 'pending' : 'success',
1514
1519
  isFetching: false,
1515
1520
  invalid: false,
1516
1521
  error: undefined,
1517
1522
  paramsError: parseErrors[index],
1518
- searchError: undefined,
1519
1523
  loadPromise: Promise.resolve(),
1520
1524
  context: undefined,
1521
1525
  abortController: new AbortController(),
@@ -1523,46 +1527,13 @@ class Router {
1523
1527
  fetchedAt: 0,
1524
1528
  cause
1525
1529
  };
1526
- return routeMatch;
1527
- });
1528
1530
 
1529
- // Take each match and resolve its search params and context
1530
- // This has to happen after the matches are created or found
1531
- // so that we can use the parent match's search params and context
1532
- matches.forEach((match, i) => {
1533
- const parentMatch = matches[i - 1];
1534
- const route = this.looseRoutesById[match.routeId];
1535
- const searchInfo = (() => {
1536
- // Validate the search params and stabilize them
1537
- const parentSearchInfo = {
1538
- search: parentMatch?.search ?? locationSearch,
1539
- routeSearch: parentMatch?.routeSearch ?? locationSearch
1540
- };
1541
- try {
1542
- const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
1543
- let routeSearch = validator?.(parentSearchInfo.search) ?? {};
1544
- let search = {
1545
- ...parentSearchInfo.search,
1546
- ...routeSearch
1547
- };
1548
- routeSearch = replaceEqualDeep(match.routeSearch, routeSearch);
1549
- search = replaceEqualDeep(match.search, search);
1550
- return {
1551
- routeSearch,
1552
- search,
1553
- searchDidChange: match.routeSearch !== routeSearch
1554
- };
1555
- } catch (err) {
1556
- match.searchError = new SearchParamError(err.message, {
1557
- cause: err
1558
- });
1559
- if (opts?.throwOnError) {
1560
- throw match.searchError;
1561
- }
1562
- return parentSearchInfo;
1563
- }
1564
- })();
1565
- Object.assign(match, searchInfo);
1531
+ // Regardless of whether we're reusing an existing match or creating
1532
+ // a new one, we need to update the match's search params
1533
+ match.search = replaceEqualDeep(match.search, preMatchSearch);
1534
+ // And also update the searchError if there is one
1535
+ match.searchError = searchError;
1536
+ matches.push(match);
1566
1537
  });
1567
1538
  return matches;
1568
1539
  };
@@ -1756,7 +1727,8 @@ class Router {
1756
1727
  loadMatches = async ({
1757
1728
  checkLatest,
1758
1729
  matches,
1759
- preload
1730
+ preload,
1731
+ invalidate
1760
1732
  }) => {
1761
1733
  let latestPromise;
1762
1734
  let firstBadMatchIndex;
@@ -1872,7 +1844,7 @@ class Router {
1872
1844
  // Default to reloading the route all the time
1873
1845
  let shouldReload = true;
1874
1846
  let shouldReloadDeps = typeof route.options.shouldReload === 'function' ? route.options.shouldReload?.(loaderContext) : !!(route.options.shouldReload ?? true);
1875
- if (match.cause === 'enter') {
1847
+ if (match.cause === 'enter' || invalidate) {
1876
1848
  match.shouldReloadDeps = shouldReloadDeps;
1877
1849
  } else if (match.cause === 'stay') {
1878
1850
  if (typeof shouldReloadDeps === 'object') {
@@ -1955,7 +1927,10 @@ class Router {
1955
1927
  await Promise.all(matchPromises);
1956
1928
  return matches;
1957
1929
  };
1958
- load = async () => {
1930
+ invalidate = () => this.load({
1931
+ invalidate: true
1932
+ });
1933
+ load = async opts => {
1959
1934
  const promise = new Promise(async (resolve, reject) => {
1960
1935
  const next = this.latestLocation;
1961
1936
  const prevLocation = this.state.resolvedLocation;
@@ -1990,7 +1965,8 @@ class Router {
1990
1965
  // Load the matches
1991
1966
  await this.loadMatches({
1992
1967
  matches,
1993
- checkLatest: () => this.checkLatest(promise)
1968
+ checkLatest: () => this.checkLatest(promise),
1969
+ invalidate: opts?.invalidate
1994
1970
  });
1995
1971
  } catch (err) {
1996
1972
  // swallow this error, since we'll display the