@tanstack/react-router 0.0.1-beta.226 → 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.
@@ -20,7 +20,6 @@ export interface RouteMatch<TRouteTree extends AnyRoute = AnyRoute, TRouteId ext
20
20
  loaderData?: RouteById<TRouteTree, TRouteId>['types']['loaderData'];
21
21
  __resolveLoadPromise?: () => void;
22
22
  context: RouteById<TRouteTree, TRouteId>['types']['allContext'];
23
- routeSearch: RouteById<TRouteTree, TRouteId>['types']['searchSchema'];
24
23
  search: FullSearchSchema<TRouteTree> & RouteById<TRouteTree, TRouteId>['types']['fullSearchSchema'];
25
24
  fetchedAt: number;
26
25
  shouldReloadDeps: any;
@@ -1,5 +1,5 @@
1
1
  import { ParsePathParams } from './link';
2
- import { AnyRoute, ResolveFullPath, ResolveFullSearchSchema, MergeFromFromParent, RouteContext, AnyContext, RouteOptions, UpdatableRouteOptions, Route, AnyPathParams, RootRouteId, TrimPathLeft, RouteConstraints } from './route';
2
+ import { AnyRoute, ResolveFullPath, ResolveFullSearchSchema, MergeFromFromParent, RouteContext, AnyContext, RouteOptions, UpdatableRouteOptions, Route, RootRouteId, TrimPathLeft, RouteConstraints } from './route';
3
3
  import { Assign, Expand, IsAny } from './utils';
4
4
  export interface FileRoutesByPath {
5
5
  }
@@ -13,7 +13,7 @@ export type FileRoutePath<TParentRoute extends AnyRoute, TFilePath extends strin
13
13
  export declare class FileRoute<TFilePath extends keyof FileRoutesByPath, TParentRoute extends AnyRoute = FileRoutesByPath[TFilePath]['parentRoute'], TId extends RouteConstraints['TId'] = TFilePath, TPath extends RouteConstraints['TPath'] = FileRoutePath<TParentRoute, TFilePath>, TFullPath extends RouteConstraints['TFullPath'] = ResolveFullPath<TParentRoute, RemoveUnderScores<TPath>>> {
14
14
  path: TFilePath;
15
15
  constructor(path: TFilePath);
16
- createRoute: <TSearchSchema extends import("./route").AnySearchSchema = {}, TFullSearchSchema extends import("./route").AnySearchSchema = ResolveFullSearchSchema<TParentRoute, TSearchSchema>, TParams extends Record<string, any> = ParsePathParams<TPath> extends never ? AnyPathParams : Record<ParsePathParams<TPath>, string>, TAllParams extends Record<string, any> = MergeFromFromParent<TParentRoute["types"]["allParams"], TParams>, TRouteContext extends RouteContext = RouteContext, TContext extends Expand<Assign<IsAny<TParentRoute["types"]["allContext"], {}>, TRouteContext>> = Expand<Assign<IsAny<TParentRoute["types"]["allContext"], {}>, TRouteContext>>, TRouterContext extends AnyContext = AnyContext, TLoaderData extends unknown = unknown, TChildren extends unknown = unknown, TRouteTree extends AnyRoute = AnyRoute>(options?: (Omit<RouteOptions<TParentRoute, string, string, TSearchSchema, TFullSearchSchema, TParams, TAllParams, TRouteContext, TContext, TLoaderData>, "path" | "id" | "getParentRoute"> & {
16
+ createRoute: <TSearchSchema extends import("./route").AnySearchSchema = {}, TFullSearchSchema extends import("./route").AnySearchSchema = ResolveFullSearchSchema<TParentRoute, TSearchSchema>, TParams extends Record<string, any> = Expand<Record<ParsePathParams<TPath>, string>>, TAllParams extends Record<string, any> = MergeFromFromParent<TParentRoute["types"]["allParams"], TParams>, TRouteContext extends RouteContext = RouteContext, TContext extends Expand<Assign<IsAny<TParentRoute["types"]["allContext"], {}>, TRouteContext>> = Expand<Assign<IsAny<TParentRoute["types"]["allContext"], {}>, TRouteContext>>, TRouterContext extends AnyContext = AnyContext, TLoaderData extends unknown = unknown, TChildren extends unknown = unknown, TRouteTree extends AnyRoute = AnyRoute>(options?: (Omit<RouteOptions<TParentRoute, string, TPath, TSearchSchema, TFullSearchSchema, TParams, TAllParams, TRouteContext, TContext, TLoaderData>, "path" | "id" | "getParentRoute"> & {
17
17
  meta?: import("./route").RouteMeta | undefined;
18
18
  } & {
19
19
  caseSensitive?: boolean | undefined;
@@ -40,7 +40,11 @@ export type BaseRouteOptions<TParentRoute extends AnyRoute = AnyRoute, TCustomId
40
40
  } : {
41
41
  beforeLoad: BeforeLoadFn<TFullSearchSchema, TParentRoute, TAllParams, TRouteContext>;
42
42
  }) & {
43
- loader?: RouteLoadFn<TAllParams, TFullSearchSchema, NoInfer<TAllContext>, NoInfer<TRouteContext>, TLoaderData>;
43
+ key?: (opts: {
44
+ search: TFullSearchSchema;
45
+ location: ParsedLocation;
46
+ }) => any;
47
+ loader?: RouteLoaderFn<TAllParams, TFullSearchSchema, NoInfer<TAllContext>, NoInfer<TRouteContext>, TLoaderData>;
44
48
  } & ({
45
49
  parseParams?: (rawParams: IsAny<TPath, any, Record<ParsePathParams<TPath>, string>>) => TParams extends Record<ParsePathParams<TPath>, any> ? TParams : 'parseParams must return an object';
46
50
  stringifyParams?: (params: NoInfer<ParamsFallback<TPath, TParams>>) => Record<ParsePathParams<TPath>, string>;
@@ -87,7 +91,7 @@ export type DefinedPathParamWarning = 'Path params cannot be redefined by child
87
91
  export type ParentParams<TParentParams> = AnyPathParams extends TParentParams ? {} : {
88
92
  [Key in keyof TParentParams]?: DefinedPathParamWarning;
89
93
  };
90
- export type RouteLoadFn<TAllParams = {}, TFullSearchSchema extends Record<string, any> = {}, TAllContext extends Record<string, any> = AnyContext, TRouteContext extends Record<string, any> = AnyContext, TLoaderData extends any = unknown> = (match: LoaderFnContext<TAllParams, TFullSearchSchema, TAllContext, TRouteContext>) => Promise<TLoaderData> | TLoaderData;
94
+ export type RouteLoaderFn<TAllParams = {}, TFullSearchSchema extends Record<string, any> = {}, TAllContext extends Record<string, any> = AnyContext, TRouteContext extends Record<string, any> = AnyContext, TLoaderData extends any = unknown> = (match: LoaderFnContext<TAllParams, TFullSearchSchema, TAllContext, TRouteContext>) => Promise<TLoaderData> | TLoaderData;
91
95
  export interface LoaderFnContext<TAllParams = {}, TFullSearchSchema extends Record<string, any> = {}, TAllContext extends Record<string, any> = AnyContext, TRouteContext extends Record<string, any> = AnyContext> {
92
96
  abortController: AbortController;
93
97
  preload: boolean;
@@ -169,7 +169,6 @@
169
169
  * @returns A history instance
170
170
  */
171
171
  function createBrowserHistory(opts) {
172
- console.log('hello');
173
172
  const getHref = opts?.getHref ?? (() => `${window.location.pathname}${window.location.search}${window.location.hash}`);
174
173
  const createHref = opts?.createHref ?? (path => path);
175
174
  let currentLocation = parseLocation(getHref(), window.history.state);
@@ -964,7 +963,6 @@
964
963
  matches: matches
965
964
  }) : null));
966
965
  }
967
- const defaultPending = () => null;
968
966
  function SafeFragment(props) {
969
967
  return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, props.children);
970
968
  }
@@ -979,9 +977,9 @@
979
977
  const routeId = match?.routeId;
980
978
  const route = routesById[routeId];
981
979
  const locationKey = useRouterState().location.state?.key;
982
- const PendingComponent = route.options.pendingComponent ?? options.defaultPendingComponent ?? defaultPending;
980
+ const PendingComponent = route.options.pendingComponent ?? options.defaultPendingComponent;
983
981
  const routeErrorComponent = route.options.errorComponent ?? options.defaultErrorComponent ?? ErrorComponent;
984
- const ResolvedSuspenseBoundary = route.options.wrapInSuspense ? React__namespace.Suspense : SafeFragment;
982
+ const ResolvedSuspenseBoundary = route.options.wrapInSuspense ?? PendingComponent ? React__namespace.Suspense : SafeFragment;
985
983
  const errorComponent = routeErrorComponent ? React__namespace.useCallback(props => {
986
984
  return /*#__PURE__*/React__namespace.createElement(routeErrorComponent, {
987
985
  ...props,
@@ -991,6 +989,7 @@
991
989
  useParams: route.useParams
992
990
  });
993
991
  }, [route]) : undefined;
992
+ const ResolvedCatchBoundary = errorComponent ? CatchBoundary : SafeFragment;
994
993
  return /*#__PURE__*/React__namespace.createElement(matchesContext.Provider, {
995
994
  value: matches
996
995
  }, /*#__PURE__*/React__namespace.createElement(ResolvedSuspenseBoundary, {
@@ -1000,7 +999,7 @@
1000
999
  useSearch: route.useSearch,
1001
1000
  useParams: route.useParams
1002
1001
  })
1003
- }, errorComponent ? /*#__PURE__*/React__namespace.createElement(CatchBoundary, {
1002
+ }, /*#__PURE__*/React__namespace.createElement(ResolvedCatchBoundary, {
1004
1003
  resetKey: locationKey,
1005
1004
  errorComponent: errorComponent,
1006
1005
  onCatch: () => {
@@ -1008,8 +1007,6 @@
1008
1007
  }
1009
1008
  }, /*#__PURE__*/React__namespace.createElement(MatchInner, {
1010
1009
  match: match
1011
- })) : /*#__PURE__*/React__namespace.createElement(SafeFragment, null, /*#__PURE__*/React__namespace.createElement(MatchInner, {
1012
- match: match
1013
1010
  }))));
1014
1011
  }
1015
1012
  function MatchInner({
@@ -1020,6 +1017,9 @@
1020
1017
  routesById
1021
1018
  } = useRouter();
1022
1019
  const route = routesById[match.routeId];
1020
+ if (match.id.split('/').length === 4) {
1021
+ console.log(match.id, pick(match, ['status', 'cause', 'isFetching']));
1022
+ }
1023
1023
  if (match.status === 'error') {
1024
1024
  throw match.error;
1025
1025
  }
@@ -1113,31 +1113,6 @@
1113
1113
  return typeof opts.select === 'function' ? opts.select(match?.loaderData) : match?.loaderData;
1114
1114
  }
1115
1115
 
1116
- // export type RouterContext<
1117
- // TRouteTree extends AnyRoute,
1118
- // // TDehydrated extends Record<string, any>,
1119
- // > = {
1120
- // buildLink: BuildLinkFn<TRouteTree>
1121
- // state: RouterState<TRouteTree>
1122
- // navigate: NavigateFn<TRouteTree>
1123
- // matchRoute: MatchRouteFn<TRouteTree>
1124
- // routeTree: TRouteTree
1125
- // routesById: RoutesById<TRouteTree>
1126
- // options: RouterOptions<TRouteTree>
1127
- // history: RouterHistory
1128
- // load: LoadFn
1129
- // buildLocation: BuildLocationFn<TRouteTree>
1130
- // subscribe: Router<TRouteTree>['subscribe']
1131
- // resetNextScrollRef: React.MutableRefObject<boolean>
1132
- // injectedHtmlRef: React.MutableRefObject<InjectedHtmlEntry[]>
1133
- // injectHtml: (entry: InjectedHtmlEntry) => void
1134
- // dehydrateData: <T>(
1135
- // key: any,
1136
- // getData: T | (() => Promise<T> | T),
1137
- // ) => () => void
1138
- // hydrateData: <T>(key: any) => T | undefined
1139
- // }
1140
-
1141
1116
  const routerContext = /*#__PURE__*/React__namespace.createContext(null);
1142
1117
  if (typeof document !== 'undefined') {
1143
1118
  window.__TSR_ROUTER_CONTEXT__ = routerContext;
@@ -1811,38 +1786,66 @@
1811
1786
  }
1812
1787
  return;
1813
1788
  });
1814
- const matches = matchedRoutes.map((route, index) => {
1789
+ const matches = [];
1790
+ matchedRoutes.forEach((route, index) => {
1791
+ // Take each matched route and resolve + validate its search params
1792
+ // This has to happen serially because each route's search params
1793
+ // can depend on the parent route's search params
1794
+ // It must also happen before we create the match so that we can
1795
+ // pass the search params to the route's potential key function
1796
+ // which is used to uniquely identify the route match in state
1797
+
1798
+ const parentMatch = matches[index - 1];
1799
+ const [preMatchSearch, searchError] = (() => {
1800
+ // Validate the search params and stabilize them
1801
+ const parentSearch = parentMatch?.search ?? locationSearch;
1802
+ try {
1803
+ const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
1804
+ let search = validator?.(parentSearch) ?? {};
1805
+ return [{
1806
+ ...parentSearch,
1807
+ ...search
1808
+ }, undefined];
1809
+ } catch (err) {
1810
+ const searchError = new SearchParamError(err.message, {
1811
+ cause: err
1812
+ });
1813
+ if (opts?.throwOnError) {
1814
+ throw searchError;
1815
+ }
1816
+ return [parentSearch, searchError];
1817
+ }
1818
+ })();
1815
1819
  const interpolatedPath = interpolatePath(route.path, routeParams);
1816
- const matchId = interpolatePath(route.id, routeParams, true);
1820
+ const matchId = interpolatePath(route.id, routeParams, true) + (route.options.key?.({
1821
+ search: preMatchSearch,
1822
+ location: this.state.location
1823
+ }) ?? '');
1817
1824
 
1818
1825
  // Waste not, want not. If we already have a match for this route,
1819
1826
  // reuse it. This is important for layout routes, which might stick
1820
1827
  // around between navigation actions that only change leaf routes.
1821
1828
  const existingMatch = getRouteMatch(this.state, matchId);
1822
1829
  const cause = this.state.matches.find(d => d.id === matchId) ? 'stay' : 'enter';
1823
- if (existingMatch) {
1824
- return {
1825
- ...existingMatch,
1826
- cause
1827
- };
1828
- }
1829
1830
 
1830
1831
  // Create a fresh route match
1831
1832
  const hasLoaders = !!(route.options.loader || componentTypes.some(d => route.options[d]?.preload));
1832
- const routeMatch = {
1833
+ const match = existingMatch ? {
1834
+ ...existingMatch,
1835
+ cause
1836
+ } : {
1833
1837
  id: matchId,
1834
1838
  routeId: route.id,
1835
1839
  params: routeParams,
1836
1840
  pathname: joinPaths([this.basepath, interpolatedPath]),
1837
1841
  updatedAt: Date.now(),
1838
- routeSearch: {},
1839
1842
  search: {},
1843
+ searchError: undefined,
1840
1844
  status: hasLoaders ? 'pending' : 'success',
1841
1845
  isFetching: false,
1842
1846
  invalid: false,
1843
1847
  error: undefined,
1844
1848
  paramsError: parseErrors[index],
1845
- searchError: undefined,
1846
1849
  loadPromise: Promise.resolve(),
1847
1850
  context: undefined,
1848
1851
  abortController: new AbortController(),
@@ -1850,46 +1853,13 @@
1850
1853
  fetchedAt: 0,
1851
1854
  cause
1852
1855
  };
1853
- return routeMatch;
1854
- });
1855
1856
 
1856
- // Take each match and resolve its search params and context
1857
- // This has to happen after the matches are created or found
1858
- // so that we can use the parent match's search params and context
1859
- matches.forEach((match, i) => {
1860
- const parentMatch = matches[i - 1];
1861
- const route = this.looseRoutesById[match.routeId];
1862
- const searchInfo = (() => {
1863
- // Validate the search params and stabilize them
1864
- const parentSearchInfo = {
1865
- search: parentMatch?.search ?? locationSearch,
1866
- routeSearch: parentMatch?.routeSearch ?? locationSearch
1867
- };
1868
- try {
1869
- const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
1870
- let routeSearch = validator?.(parentSearchInfo.search) ?? {};
1871
- let search = {
1872
- ...parentSearchInfo.search,
1873
- ...routeSearch
1874
- };
1875
- routeSearch = replaceEqualDeep(match.routeSearch, routeSearch);
1876
- search = replaceEqualDeep(match.search, search);
1877
- return {
1878
- routeSearch,
1879
- search,
1880
- searchDidChange: match.routeSearch !== routeSearch
1881
- };
1882
- } catch (err) {
1883
- match.searchError = new SearchParamError(err.message, {
1884
- cause: err
1885
- });
1886
- if (opts?.throwOnError) {
1887
- throw match.searchError;
1888
- }
1889
- return parentSearchInfo;
1890
- }
1891
- })();
1892
- Object.assign(match, searchInfo);
1857
+ // Regardless of whether we're reusing an existing match or creating
1858
+ // a new one, we need to update the match's search params
1859
+ match.search = replaceEqualDeep(match.search, preMatchSearch);
1860
+ // And also update the searchError if there is one
1861
+ match.searchError = searchError;
1862
+ matches.push(match);
1893
1863
  });
1894
1864
  return matches;
1895
1865
  };