@tanstack/react-router 0.0.1-beta.273 → 0.0.1-beta.275

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.
@@ -1080,9 +1080,13 @@
1080
1080
  });
1081
1081
  }
1082
1082
 
1083
- const routerContext = /*#__PURE__*/React__namespace.createContext(null);
1083
+ exports.routerContext = /*#__PURE__*/React__namespace.createContext(null);
1084
1084
  if (typeof document !== 'undefined') {
1085
- window.__TSR_ROUTER_CONTEXT__ = routerContext;
1085
+ if (window.__TSR_ROUTER_CONTEXT__) {
1086
+ exports.routerContext = window.__TSR_ROUTER_CONTEXT__;
1087
+ } else {
1088
+ window.__TSR_ROUTER_CONTEXT__ = exports.routerContext;
1089
+ }
1086
1090
  }
1087
1091
  function RouterProvider({
1088
1092
  router,
@@ -1098,7 +1102,7 @@
1098
1102
  }
1099
1103
  });
1100
1104
  const matches = router.options.InnerWrap ? /*#__PURE__*/React__namespace.createElement(router.options.InnerWrap, null, /*#__PURE__*/React__namespace.createElement(Matches, null)) : /*#__PURE__*/React__namespace.createElement(Matches, null);
1101
- const provider = /*#__PURE__*/React__namespace.createElement(routerContext.Provider, {
1105
+ const provider = /*#__PURE__*/React__namespace.createElement(exports.routerContext.Provider, {
1102
1106
  value: router
1103
1107
  }, matches, /*#__PURE__*/React__namespace.createElement(Transitioner, null));
1104
1108
  if (router.options.Wrap) {
@@ -1192,14 +1196,14 @@
1192
1196
  return null;
1193
1197
  }
1194
1198
  function getRouteMatch(state, id) {
1195
- return [...(state.pendingMatches ?? []), ...state.matches].find(d => d.id === id);
1199
+ return [...state.preloadMatches, ...(state.pendingMatches ?? []), ...state.matches].find(d => d.id === id);
1196
1200
  }
1197
1201
  function useRouterState(opts) {
1198
1202
  const router = useRouter();
1199
1203
  return useStore(router.__store, opts?.select);
1200
1204
  }
1201
1205
  function useRouter() {
1202
- const resolvedContext = typeof document !== 'undefined' ? window.__TSR_ROUTER_CONTEXT__ || routerContext : routerContext;
1206
+ const resolvedContext = typeof document !== 'undefined' ? window.__TSR_ROUTER_CONTEXT__ || exports.routerContext : exports.routerContext;
1203
1207
  const value = React__namespace.useContext(resolvedContext);
1204
1208
  warning(value, 'useRouter must be used inside a <RouterProvider> component!');
1205
1209
  return value;
@@ -2261,11 +2265,18 @@
2261
2265
  return [parentSearch, searchError];
2262
2266
  }
2263
2267
  })();
2268
+
2269
+ // This is where we need to call route.options.loaderDeps() to get any additional
2270
+ // deps that the route's loader function might need to run. We need to do this
2271
+ // before we create the match so that we can pass the deps to the route's
2272
+ // potential key function which is used to uniquely identify the route match in state
2273
+
2274
+ const loaderDeps = route.options.loaderDeps?.({
2275
+ search: preMatchSearch
2276
+ }) ?? '';
2277
+ const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : '';
2264
2278
  const interpolatedPath = interpolatePath(route.fullPath, routeParams);
2265
- const matchId = interpolatePath(route.id, routeParams, true) + (route.options.key?.({
2266
- search: preMatchSearch,
2267
- location: this.state.location
2268
- }) ?? '');
2279
+ const matchId = interpolatePath(route.id, routeParams, true) + loaderDepsHash;
2269
2280
 
2270
2281
  // Waste not, want not. If we already have a match for this route,
2271
2282
  // reuse it. This is important for layout routes, which might stick
@@ -2296,8 +2307,9 @@
2296
2307
  context: undefined,
2297
2308
  abortController: new AbortController(),
2298
2309
  shouldReloadDeps: undefined,
2299
- fetchedAt: 0,
2300
- cause
2310
+ fetchCount: 0,
2311
+ cause,
2312
+ loaderDeps
2301
2313
  };
2302
2314
 
2303
2315
  // Regardless of whether we're reusing an existing match or creating
@@ -2505,10 +2517,13 @@
2505
2517
  }) => {
2506
2518
  let latestPromise;
2507
2519
  let firstBadMatchIndex;
2508
- const updatePendingMatch = match => {
2520
+ const updateMatch = match => {
2521
+ const isPreload = this.state.preloadMatches.find(d => d.id === match.id);
2522
+ const isPending = this.state.pendingMatches?.find(d => d.id === match.id);
2523
+ const matchesKey = isPreload ? 'preloadMatches' : isPending ? 'pendingMatches' : 'matches';
2509
2524
  this.__store.setState(s => ({
2510
2525
  ...s,
2511
- pendingMatches: s.pendingMatches?.map(d => d.id === match.id ? match : d)
2526
+ [matchesKey]: s[matchesKey]?.map(d => d.id === match.id ? match : d)
2512
2527
  }));
2513
2528
  };
2514
2529
 
@@ -2561,7 +2576,7 @@
2561
2576
  from: match.pathname
2562
2577
  }),
2563
2578
  buildLocation: this.buildLocation,
2564
- cause: match.cause
2579
+ cause: preload ? 'preload' : match.cause
2565
2580
  })) ?? {};
2566
2581
  if (isRedirect(beforeLoadContext)) {
2567
2582
  throw beforeLoadContext;
@@ -2591,7 +2606,7 @@
2591
2606
  const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
2592
2607
  const matchPromises = [];
2593
2608
  validResolvedMatches.forEach((match, index) => {
2594
- matchPromises.push((async () => {
2609
+ matchPromises.push(new Promise(async resolve => {
2595
2610
  const parentMatchPromise = matchPromises[index - 1];
2596
2611
  const route = this.looseRoutesById[match.routeId];
2597
2612
  const handleErrorAndRedirect = err => {
@@ -2606,92 +2621,95 @@
2606
2621
  let loadPromise;
2607
2622
  matches[index] = match = {
2608
2623
  ...match,
2609
- fetchedAt: Date.now(),
2610
2624
  showPending: false
2611
2625
  };
2626
+ let didShowPending = false;
2612
2627
  const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
2613
- let pendingPromise;
2614
- if (!preload && pendingMs && (route.options.pendingComponent ?? this.options.defaultPendingComponent)) {
2615
- pendingPromise = new Promise(r => setTimeout(r, pendingMs));
2616
- }
2617
- if (match.isFetching) {
2618
- loadPromise = getRouteMatch(this.state, match.id)?.loadPromise;
2619
- } else {
2620
- const loaderContext = {
2621
- params: match.params,
2622
- search: match.search,
2623
- preload: !!preload,
2624
- parentMatchPromise,
2625
- abortController: match.abortController,
2626
- context: match.context,
2627
- location: this.state.location,
2628
- navigate: opts => this.navigate({
2629
- ...opts,
2630
- from: match.pathname
2631
- }),
2632
- cause: match.cause
2633
- };
2628
+ const pendingMinMs = route.options.pendingMinMs ?? this.options.defaultPendingMinMs;
2629
+ const shouldPending = !preload && pendingMs && (route.options.pendingComponent ?? this.options.defaultPendingComponent);
2630
+ const fetch = async () => {
2631
+ if (match.isFetching) {
2632
+ loadPromise = getRouteMatch(this.state, match.id)?.loadPromise;
2633
+ } else {
2634
+ const loaderContext = {
2635
+ params: match.params,
2636
+ deps: match.loaderDeps,
2637
+ preload: !!preload,
2638
+ parentMatchPromise,
2639
+ abortController: match.abortController,
2640
+ context: match.context,
2641
+ location: this.state.location,
2642
+ navigate: opts => this.navigate({
2643
+ ...opts,
2644
+ from: match.pathname
2645
+ }),
2646
+ cause: preload ? 'preload' : match.cause
2647
+ };
2634
2648
 
2635
- // Default to reloading the route all the time
2636
- let shouldReload = true;
2637
- let shouldReloadDeps = typeof route.options.shouldReload === 'function' ? route.options.shouldReload?.(loaderContext) : !!(route.options.shouldReload ?? true);
2638
- if (match.cause === 'enter' || invalidate) {
2639
- match.shouldReloadDeps = shouldReloadDeps;
2640
- } else if (match.cause === 'stay') {
2641
- if (typeof shouldReloadDeps === 'object') {
2642
- // compare the deps to see if they've changed
2643
- shouldReload = !deepEqual(shouldReloadDeps, match.shouldReloadDeps);
2644
- match.shouldReloadDeps = shouldReloadDeps;
2649
+ // Default to reloading the route all the time
2650
+ let shouldLoad = true;
2651
+ const shouldReloadFn = route.options.shouldReload;
2652
+ let shouldReloadDeps = typeof shouldReloadFn === 'function' ? shouldReloadFn(loaderContext) : !!(shouldReloadFn ?? true);
2653
+ const compareDeps = () => {
2654
+ if (typeof shouldReloadDeps === 'object') {
2655
+ // compare the deps to see if they've changed
2656
+ shouldLoad = !deepEqual(shouldReloadDeps, match.shouldReloadDeps);
2657
+ } else {
2658
+ shouldLoad = !!shouldReloadDeps;
2659
+ }
2660
+ };
2661
+
2662
+ // If it's the first preload, or the route is entering, or we're
2663
+ // invalidating, we definitely need to load the route
2664
+ if (invalidate) ; else if (preload) {
2665
+ if (!match.fetchCount) ; else {
2666
+ compareDeps();
2667
+ }
2668
+ } else if (match.cause === 'enter') {
2669
+ if (!match.fetchCount) ; else {
2670
+ compareDeps();
2671
+ }
2645
2672
  } else {
2646
- shouldReload = !!shouldReloadDeps;
2673
+ compareDeps();
2674
+ }
2675
+ if (typeof shouldReloadDeps === 'object') {
2676
+ matches[index] = match = {
2677
+ ...match,
2678
+ shouldReloadDeps
2679
+ };
2647
2680
  }
2648
- }
2649
2681
 
2650
- // If the user doesn't want the route to reload, just
2651
- // resolve with the existing loader data
2682
+ // If the user doesn't want the route to reload, just
2683
+ // resolve with the existing loader data
2652
2684
 
2653
- if (!shouldReload) {
2654
- loadPromise = Promise.resolve(match.loaderData);
2655
- } else {
2656
- // Otherwise, load the route
2657
- matches[index] = match = {
2658
- ...match,
2659
- isFetching: true
2660
- };
2661
- const componentsPromise = Promise.all(componentTypes.map(async type => {
2662
- const component = route.options[type];
2663
- if (component?.preload) {
2664
- await component.preload();
2685
+ if (!shouldLoad) {
2686
+ loadPromise = Promise.resolve(match.loaderData);
2687
+ } else {
2688
+ if (match.fetchCount && match.status === 'success') {
2689
+ resolve();
2665
2690
  }
2666
- }));
2667
- const loaderPromise = route.options.loader?.(loaderContext);
2668
- loadPromise = Promise.all([componentsPromise, loaderPromise]).then(d => d[1]);
2669
- }
2670
- }
2671
- matches[index] = match = {
2672
- ...match,
2673
- loadPromise
2674
- };
2675
- if (!preload) {
2676
- updatePendingMatch(match);
2677
- }
2678
- let didShowPending = false;
2679
- const pendingMinMs = route.options.pendingMinMs ?? this.options.defaultPendingMinMs;
2680
- await new Promise(async resolve => {
2681
- // If the route has a pending component and a pendingMs option,
2682
- // forcefully show the pending component
2683
- if (pendingPromise) {
2684
- pendingPromise.then(() => {
2685
- if (latestPromise = checkLatest()) return;
2686
- didShowPending = true;
2691
+
2692
+ // Otherwise, load the route
2687
2693
  matches[index] = match = {
2688
2694
  ...match,
2689
- showPending: true
2695
+ isFetching: true,
2696
+ fetchCount: match.fetchCount + 1
2690
2697
  };
2691
- updatePendingMatch(match);
2692
- resolve();
2693
- });
2698
+ const componentsPromise = Promise.all(componentTypes.map(async type => {
2699
+ const component = route.options[type];
2700
+ if (component?.preload) {
2701
+ await component.preload();
2702
+ }
2703
+ }));
2704
+ const loaderPromise = route.options.loader?.(loaderContext);
2705
+ loadPromise = Promise.all([componentsPromise, loaderPromise]).then(d => d[1]);
2706
+ }
2694
2707
  }
2708
+ matches[index] = match = {
2709
+ ...match,
2710
+ loadPromise
2711
+ };
2712
+ updateMatch(match);
2695
2713
  try {
2696
2714
  const loaderData = await loadPromise;
2697
2715
  if (latestPromise = checkLatest()) return await latestPromise;
@@ -2732,18 +2750,38 @@
2732
2750
  // we already moved the pendingMatches to the matches
2733
2751
  // state, so we need to update that specific match
2734
2752
  if (didShowPending && pendingMinMs && match.showPending) {
2735
- this.__store.setState(s => ({
2736
- ...s,
2737
- matches: s.matches?.map(d => d.id === match.id ? match : d)
2738
- }));
2753
+ updateMatch(match);
2739
2754
  }
2740
2755
  }
2741
- if (!preload) {
2742
- updatePendingMatch(match);
2756
+ updateMatch(match);
2757
+ };
2758
+ if (match.fetchCount && match.status === 'success') {
2759
+ // Background Fetching
2760
+ fetch();
2761
+ } else {
2762
+ // Critical Fetching
2763
+
2764
+ // If we need to potentially show the pending component,
2765
+ // start a timer to show it after the pendingMs
2766
+ if (shouldPending) {
2767
+ new Promise(r => setTimeout(r, pendingMs)).then(async () => {
2768
+ if (latestPromise = checkLatest()) return latestPromise;
2769
+ didShowPending = true;
2770
+ matches[index] = match = {
2771
+ ...match,
2772
+ showPending: true
2773
+ };
2774
+ updateMatch(match);
2775
+ resolve();
2776
+ });
2743
2777
  }
2744
- resolve();
2745
- });
2746
- })());
2778
+ await fetch();
2779
+ }
2780
+ resolve();
2781
+ // No Fetching
2782
+
2783
+ resolve();
2784
+ }));
2747
2785
  });
2748
2786
  await Promise.all(matchPromises);
2749
2787
  return matches;
@@ -2766,20 +2804,32 @@
2766
2804
  toLocation: next,
2767
2805
  pathChanged: pathDidChange
2768
2806
  });
2769
-
2770
- // Match the routes
2771
- let pendingMatches = this.matchRoutes(next.pathname, next.search, {
2772
- debug: true
2773
- });
2807
+ let pendingMatches;
2774
2808
  const previousMatches = this.state.matches;
2809
+ this.__store.batch(() => {
2810
+ this.__store.setState(s => ({
2811
+ ...s,
2812
+ preloadMatches: s.preloadMatches.filter(d => {
2813
+ return Date.now() - d.updatedAt < (this.options.defaultPreloadMaxAge ?? 3000);
2814
+ })
2815
+ }));
2775
2816
 
2776
- // Ingest the new matches
2777
- this.__store.setState(s => ({
2778
- ...s,
2779
- isLoading: true,
2780
- location: next,
2781
- pendingMatches
2782
- }));
2817
+ // Match the routes
2818
+ pendingMatches = this.matchRoutes(next.pathname, next.search, {
2819
+ debug: true
2820
+ });
2821
+
2822
+ // Ingest the new matches
2823
+ this.__store.setState(s => ({
2824
+ ...s,
2825
+ isLoading: true,
2826
+ location: next,
2827
+ pendingMatches,
2828
+ preloadMatches: s.preloadMatches.filter(d => {
2829
+ return !pendingMatches.find(e => e.id === d.id);
2830
+ })
2831
+ }));
2832
+ });
2783
2833
  try {
2784
2834
  try {
2785
2835
  // Load the matches
@@ -2837,6 +2887,17 @@
2837
2887
  let matches = this.matchRoutes(next.pathname, next.search, {
2838
2888
  throwOnError: true
2839
2889
  });
2890
+ const loadedMatchIds = Object.fromEntries([...this.state.matches, ...(this.state.pendingMatches ?? []), ...this.state.preloadMatches]?.map(d => [d.id, true]));
2891
+ this.__store.batch(() => {
2892
+ matches.forEach(match => {
2893
+ if (!loadedMatchIds[match.id]) {
2894
+ this.__store.setState(s => ({
2895
+ ...s,
2896
+ preloadMatches: [...s.preloadMatches, match]
2897
+ }));
2898
+ }
2899
+ });
2900
+ });
2840
2901
  matches = await this.loadMatches({
2841
2902
  matches,
2842
2903
  preload: true,
@@ -2899,7 +2960,7 @@
2899
2960
  dehydrate = () => {
2900
2961
  return {
2901
2962
  state: {
2902
- dehydratedMatches: this.state.matches.map(d => pick(d, ['fetchedAt', 'id', 'status', 'updatedAt', 'loaderData']))
2963
+ dehydratedMatches: this.state.matches.map(d => pick(d, ['id', 'status', 'updatedAt', 'loaderData']))
2903
2964
  }
2904
2965
  };
2905
2966
  };
@@ -2962,6 +3023,7 @@
2962
3023
  location,
2963
3024
  matches: [],
2964
3025
  pendingMatches: [],
3026
+ preloadMatches: [],
2965
3027
  lastUpdated: Date.now()
2966
3028
  };
2967
3029
  }
@@ -3255,7 +3317,6 @@
3255
3317
  exports.resolvePath = resolvePath;
3256
3318
  exports.rootRouteId = rootRouteId;
3257
3319
  exports.rootRouteWithContext = rootRouteWithContext;
3258
- exports.routerContext = routerContext;
3259
3320
  exports.shallow = shallow;
3260
3321
  exports.stringifySearchWith = stringifySearchWith;
3261
3322
  exports.trimPath = trimPath;