@tanstack/react-router 0.0.1-beta.272 → 0.0.1-beta.274

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.
@@ -472,9 +472,13 @@ function useLoaderData(opts) {
472
472
  });
473
473
  }
474
474
 
475
- const routerContext = /*#__PURE__*/React.createContext(null);
475
+ let routerContext = /*#__PURE__*/React.createContext(null);
476
476
  if (typeof document !== 'undefined') {
477
- window.__TSR_ROUTER_CONTEXT__ = routerContext;
477
+ if (window.__TSR_ROUTER_CONTEXT__) {
478
+ routerContext = window.__TSR_ROUTER_CONTEXT__;
479
+ } else {
480
+ window.__TSR_ROUTER_CONTEXT__ = routerContext;
481
+ }
478
482
  }
479
483
  function RouterProvider({
480
484
  router,
@@ -561,9 +565,11 @@ function Transitioner() {
561
565
  pathChanged: routerState.location.href !== routerState.resolvedLocation?.href
562
566
  });
563
567
  if (document.querySelector) {
564
- const el = document.getElementById(routerState.location.hash);
565
- if (el) {
566
- el.scrollIntoView();
568
+ if (routerState.location.hash !== '') {
569
+ const el = document.getElementById(routerState.location.hash);
570
+ if (el) {
571
+ el.scrollIntoView();
572
+ }
567
573
  }
568
574
  }
569
575
  router.pendingMatches = [];
@@ -582,7 +588,7 @@ function Transitioner() {
582
588
  return null;
583
589
  }
584
590
  function getRouteMatch(state, id) {
585
- return [...(state.pendingMatches ?? []), ...state.matches].find(d => d.id === id);
591
+ return [...state.preloadMatches, ...(state.pendingMatches ?? []), ...state.matches].find(d => d.id === id);
586
592
  }
587
593
  function useRouterState(opts) {
588
594
  const router = useRouter();
@@ -1686,7 +1692,7 @@ class Router {
1686
1692
  context: undefined,
1687
1693
  abortController: new AbortController(),
1688
1694
  shouldReloadDeps: undefined,
1689
- fetchedAt: 0,
1695
+ fetchCount: 0,
1690
1696
  cause
1691
1697
  };
1692
1698
 
@@ -1895,10 +1901,13 @@ class Router {
1895
1901
  }) => {
1896
1902
  let latestPromise;
1897
1903
  let firstBadMatchIndex;
1898
- const updatePendingMatch = match => {
1904
+ const updateMatch = match => {
1905
+ const isPreload = this.state.preloadMatches.find(d => d.id === match.id);
1906
+ const isPending = this.state.pendingMatches?.find(d => d.id === match.id);
1907
+ const matchesKey = isPreload ? 'preloadMatches' : isPending ? 'pendingMatches' : 'matches';
1899
1908
  this.__store.setState(s => ({
1900
1909
  ...s,
1901
- pendingMatches: s.pendingMatches?.map(d => d.id === match.id ? match : d)
1910
+ [matchesKey]: s[matchesKey]?.map(d => d.id === match.id ? match : d)
1902
1911
  }));
1903
1912
  };
1904
1913
 
@@ -1951,7 +1960,7 @@ class Router {
1951
1960
  from: match.pathname
1952
1961
  }),
1953
1962
  buildLocation: this.buildLocation,
1954
- cause: match.cause
1963
+ cause: preload ? 'preload' : match.cause
1955
1964
  })) ?? {};
1956
1965
  if (isRedirect(beforeLoadContext)) {
1957
1966
  throw beforeLoadContext;
@@ -1981,7 +1990,7 @@ class Router {
1981
1990
  const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
1982
1991
  const matchPromises = [];
1983
1992
  validResolvedMatches.forEach((match, index) => {
1984
- matchPromises.push((async () => {
1993
+ matchPromises.push(new Promise(async resolve => {
1985
1994
  const parentMatchPromise = matchPromises[index - 1];
1986
1995
  const route = this.looseRoutesById[match.routeId];
1987
1996
  const handleErrorAndRedirect = err => {
@@ -1996,92 +2005,95 @@ class Router {
1996
2005
  let loadPromise;
1997
2006
  matches[index] = match = {
1998
2007
  ...match,
1999
- fetchedAt: Date.now(),
2000
2008
  showPending: false
2001
2009
  };
2010
+ let didShowPending = false;
2002
2011
  const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
2003
- let pendingPromise;
2004
- if (!preload && pendingMs && (route.options.pendingComponent ?? this.options.defaultPendingComponent)) {
2005
- pendingPromise = new Promise(r => setTimeout(r, pendingMs));
2006
- }
2007
- if (match.isFetching) {
2008
- loadPromise = getRouteMatch(this.state, match.id)?.loadPromise;
2009
- } else {
2010
- const loaderContext = {
2011
- params: match.params,
2012
- search: match.search,
2013
- preload: !!preload,
2014
- parentMatchPromise,
2015
- abortController: match.abortController,
2016
- context: match.context,
2017
- location: this.state.location,
2018
- navigate: opts => this.navigate({
2019
- ...opts,
2020
- from: match.pathname
2021
- }),
2022
- cause: match.cause
2023
- };
2012
+ const pendingMinMs = route.options.pendingMinMs ?? this.options.defaultPendingMinMs;
2013
+ const shouldPending = !preload && pendingMs && (route.options.pendingComponent ?? this.options.defaultPendingComponent);
2014
+ const fetch = async () => {
2015
+ if (match.isFetching) {
2016
+ loadPromise = getRouteMatch(this.state, match.id)?.loadPromise;
2017
+ } else {
2018
+ const loaderContext = {
2019
+ params: match.params,
2020
+ search: match.search,
2021
+ preload: !!preload,
2022
+ parentMatchPromise,
2023
+ abortController: match.abortController,
2024
+ context: match.context,
2025
+ location: this.state.location,
2026
+ navigate: opts => this.navigate({
2027
+ ...opts,
2028
+ from: match.pathname
2029
+ }),
2030
+ cause: preload ? 'preload' : match.cause
2031
+ };
2024
2032
 
2025
- // Default to reloading the route all the time
2026
- let shouldReload = true;
2027
- let shouldReloadDeps = typeof route.options.shouldReload === 'function' ? route.options.shouldReload?.(loaderContext) : !!(route.options.shouldReload ?? true);
2028
- if (match.cause === 'enter' || invalidate) {
2029
- match.shouldReloadDeps = shouldReloadDeps;
2030
- } else if (match.cause === 'stay') {
2031
- if (typeof shouldReloadDeps === 'object') {
2032
- // compare the deps to see if they've changed
2033
- shouldReload = !deepEqual(shouldReloadDeps, match.shouldReloadDeps);
2034
- match.shouldReloadDeps = shouldReloadDeps;
2033
+ // Default to reloading the route all the time
2034
+ let shouldLoad = true;
2035
+ const shouldReloadFn = route.options.shouldReload;
2036
+ let shouldReloadDeps = typeof shouldReloadFn === 'function' ? shouldReloadFn(loaderContext) : !!(shouldReloadFn ?? true);
2037
+ const compareDeps = () => {
2038
+ if (typeof shouldReloadDeps === 'object') {
2039
+ // compare the deps to see if they've changed
2040
+ shouldLoad = !deepEqual(shouldReloadDeps, match.shouldReloadDeps);
2041
+ } else {
2042
+ shouldLoad = !!shouldReloadDeps;
2043
+ }
2044
+ };
2045
+
2046
+ // If it's the first preload, or the route is entering, or we're
2047
+ // invalidating, we definitely need to load the route
2048
+ if (invalidate) ; else if (preload) {
2049
+ if (!match.fetchCount) ; else {
2050
+ compareDeps();
2051
+ }
2052
+ } else if (match.cause === 'enter') {
2053
+ if (!match.fetchCount) ; else {
2054
+ compareDeps();
2055
+ }
2035
2056
  } else {
2036
- shouldReload = !!shouldReloadDeps;
2057
+ compareDeps();
2058
+ }
2059
+ if (typeof shouldReloadDeps === 'object') {
2060
+ matches[index] = match = {
2061
+ ...match,
2062
+ shouldReloadDeps
2063
+ };
2037
2064
  }
2038
- }
2039
2065
 
2040
- // If the user doesn't want the route to reload, just
2041
- // resolve with the existing loader data
2066
+ // If the user doesn't want the route to reload, just
2067
+ // resolve with the existing loader data
2042
2068
 
2043
- if (!shouldReload) {
2044
- loadPromise = Promise.resolve(match.loaderData);
2045
- } else {
2046
- // Otherwise, load the route
2047
- matches[index] = match = {
2048
- ...match,
2049
- isFetching: true
2050
- };
2051
- const componentsPromise = Promise.all(componentTypes.map(async type => {
2052
- const component = route.options[type];
2053
- if (component?.preload) {
2054
- await component.preload();
2069
+ if (!shouldLoad) {
2070
+ loadPromise = Promise.resolve(match.loaderData);
2071
+ } else {
2072
+ if (match.fetchCount && match.status === 'success') {
2073
+ resolve();
2055
2074
  }
2056
- }));
2057
- const loaderPromise = route.options.loader?.(loaderContext);
2058
- loadPromise = Promise.all([componentsPromise, loaderPromise]).then(d => d[1]);
2059
- }
2060
- }
2061
- matches[index] = match = {
2062
- ...match,
2063
- loadPromise
2064
- };
2065
- if (!preload) {
2066
- updatePendingMatch(match);
2067
- }
2068
- let didShowPending = false;
2069
- const pendingMinMs = route.options.pendingMinMs ?? this.options.defaultPendingMinMs;
2070
- await new Promise(async resolve => {
2071
- // If the route has a pending component and a pendingMs option,
2072
- // forcefully show the pending component
2073
- if (pendingPromise) {
2074
- pendingPromise.then(() => {
2075
- if (latestPromise = checkLatest()) return;
2076
- didShowPending = true;
2075
+
2076
+ // Otherwise, load the route
2077
2077
  matches[index] = match = {
2078
2078
  ...match,
2079
- showPending: true
2079
+ isFetching: true,
2080
+ fetchCount: match.fetchCount + 1
2080
2081
  };
2081
- updatePendingMatch(match);
2082
- resolve();
2083
- });
2082
+ const componentsPromise = Promise.all(componentTypes.map(async type => {
2083
+ const component = route.options[type];
2084
+ if (component?.preload) {
2085
+ await component.preload();
2086
+ }
2087
+ }));
2088
+ const loaderPromise = route.options.loader?.(loaderContext);
2089
+ loadPromise = Promise.all([componentsPromise, loaderPromise]).then(d => d[1]);
2090
+ }
2084
2091
  }
2092
+ matches[index] = match = {
2093
+ ...match,
2094
+ loadPromise
2095
+ };
2096
+ updateMatch(match);
2085
2097
  try {
2086
2098
  const loaderData = await loadPromise;
2087
2099
  if (latestPromise = checkLatest()) return await latestPromise;
@@ -2122,18 +2134,38 @@ class Router {
2122
2134
  // we already moved the pendingMatches to the matches
2123
2135
  // state, so we need to update that specific match
2124
2136
  if (didShowPending && pendingMinMs && match.showPending) {
2125
- this.__store.setState(s => ({
2126
- ...s,
2127
- matches: s.matches?.map(d => d.id === match.id ? match : d)
2128
- }));
2137
+ updateMatch(match);
2129
2138
  }
2130
2139
  }
2131
- if (!preload) {
2132
- updatePendingMatch(match);
2140
+ updateMatch(match);
2141
+ };
2142
+ if (match.fetchCount && match.status === 'success') {
2143
+ // Background Fetching
2144
+ fetch();
2145
+ } else {
2146
+ // Critical Fetching
2147
+
2148
+ // If we need to potentially show the pending component,
2149
+ // start a timer to show it after the pendingMs
2150
+ if (shouldPending) {
2151
+ new Promise(r => setTimeout(r, pendingMs)).then(async () => {
2152
+ if (latestPromise = checkLatest()) return latestPromise;
2153
+ didShowPending = true;
2154
+ matches[index] = match = {
2155
+ ...match,
2156
+ showPending: true
2157
+ };
2158
+ updateMatch(match);
2159
+ resolve();
2160
+ });
2133
2161
  }
2134
- resolve();
2135
- });
2136
- })());
2162
+ await fetch();
2163
+ }
2164
+ resolve();
2165
+ // No Fetching
2166
+
2167
+ resolve();
2168
+ }));
2137
2169
  });
2138
2170
  await Promise.all(matchPromises);
2139
2171
  return matches;
@@ -2156,20 +2188,32 @@ class Router {
2156
2188
  toLocation: next,
2157
2189
  pathChanged: pathDidChange
2158
2190
  });
2159
-
2160
- // Match the routes
2161
- let pendingMatches = this.matchRoutes(next.pathname, next.search, {
2162
- debug: true
2163
- });
2191
+ let pendingMatches;
2164
2192
  const previousMatches = this.state.matches;
2193
+ this.__store.batch(() => {
2194
+ this.__store.setState(s => ({
2195
+ ...s,
2196
+ preloadMatches: s.preloadMatches.filter(d => {
2197
+ return Date.now() - d.updatedAt < (this.options.defaultPreloadMaxAge ?? 3000);
2198
+ })
2199
+ }));
2165
2200
 
2166
- // Ingest the new matches
2167
- this.__store.setState(s => ({
2168
- ...s,
2169
- isLoading: true,
2170
- location: next,
2171
- pendingMatches
2172
- }));
2201
+ // Match the routes
2202
+ pendingMatches = this.matchRoutes(next.pathname, next.search, {
2203
+ debug: true
2204
+ });
2205
+
2206
+ // Ingest the new matches
2207
+ this.__store.setState(s => ({
2208
+ ...s,
2209
+ isLoading: true,
2210
+ location: next,
2211
+ pendingMatches,
2212
+ preloadMatches: s.preloadMatches.filter(d => {
2213
+ return !pendingMatches.find(e => e.id === d.id);
2214
+ })
2215
+ }));
2216
+ });
2173
2217
  try {
2174
2218
  try {
2175
2219
  // Load the matches
@@ -2227,6 +2271,17 @@ class Router {
2227
2271
  let matches = this.matchRoutes(next.pathname, next.search, {
2228
2272
  throwOnError: true
2229
2273
  });
2274
+ const loadedMatchIds = Object.fromEntries([...this.state.matches, ...(this.state.pendingMatches ?? []), ...this.state.preloadMatches]?.map(d => [d.id, true]));
2275
+ this.__store.batch(() => {
2276
+ matches.forEach(match => {
2277
+ if (!loadedMatchIds[match.id]) {
2278
+ this.__store.setState(s => ({
2279
+ ...s,
2280
+ preloadMatches: [...s.preloadMatches, match]
2281
+ }));
2282
+ }
2283
+ });
2284
+ });
2230
2285
  matches = await this.loadMatches({
2231
2286
  matches,
2232
2287
  preload: true,
@@ -2289,7 +2344,7 @@ class Router {
2289
2344
  dehydrate = () => {
2290
2345
  return {
2291
2346
  state: {
2292
- dehydratedMatches: this.state.matches.map(d => pick(d, ['fetchedAt', 'id', 'status', 'updatedAt', 'loaderData']))
2347
+ dehydratedMatches: this.state.matches.map(d => pick(d, ['id', 'status', 'updatedAt', 'loaderData']))
2293
2348
  }
2294
2349
  };
2295
2350
  };
@@ -2352,6 +2407,7 @@ function getInitialRouterState(location) {
2352
2407
  location,
2353
2408
  matches: [],
2354
2409
  pendingMatches: [],
2410
+ preloadMatches: [],
2355
2411
  lastUpdated: Date.now()
2356
2412
  };
2357
2413
  }