@remix-run/router 1.11.0-pre.0 → 1.11.0-pre.1

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,14 @@
1
1
  # `@remix-run/router`
2
2
 
3
+ ## 1.11.0-pre.1
4
+
5
+ ### Minor Changes
6
+
7
+ - When `v7_fetcherPersist` is enabled, the router now performs ref-counting on fetcher keys via `getFetcher`/`deleteFetcher` so it knows when a given fetcher is totally unmounted from the UI ([#10977](https://github.com/remix-run/react-router/pull/10977))
8
+
9
+ - Once a fetcher has been totally unmounted, we can ignore post-processing of a persisted fetcher result such as a redirect or an error
10
+ - The router will also pass a new `deletedFetchers` array to the subscriber callbacks so that the UI layer can remove associated fetcher data
11
+
3
12
  ## 1.11.0-pre.0
4
13
 
5
14
  ### Minor Changes
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v1.11.0-pre.0
2
+ * @remix-run/router v1.11.0-pre.1
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1783,6 +1783,9 @@ function createRouter(init) {
1783
1783
  // Most recent href/match for fetcher.load calls for fetchers
1784
1784
  let fetchLoadMatches = new Map();
1785
1785
 
1786
+ // Ref-count mounted fetchers so we know when it's ok to clean them up
1787
+ let activeFetchers = new Map();
1788
+
1786
1789
  // Fetchers that have requested a delete when using v7_fetcherPersist,
1787
1790
  // they'll be officially removed after they return to idle
1788
1791
  let deletedFetchers = new Set();
@@ -1899,27 +1902,35 @@ function createRouter(init) {
1899
1902
  // Update our state and notify the calling context of the change
1900
1903
  function updateState(newState, viewTransitionOpts) {
1901
1904
  state = _extends({}, state, newState);
1902
- subscribers.forEach(subscriber => subscriber(state, {
1903
- unstable_viewTransitionOpts: viewTransitionOpts
1904
- }));
1905
1905
 
1906
- // Remove idle fetchers from state since we only care about in-flight fetchers.
1906
+ // Prep fetcher cleanup so we can tell the UI which fetcher data entries
1907
+ // can be removed
1908
+ let completedFetchers = [];
1909
+ let deletedFetchersKeys = [];
1907
1910
  if (future.v7_fetcherPersist) {
1908
1911
  state.fetchers.forEach((fetcher, key) => {
1909
1912
  if (fetcher.state === "idle") {
1910
1913
  if (deletedFetchers.has(key)) {
1911
- // If the fetcher has unmounted and called router.deleteFetcher(),
1912
- // we can totally delete the fetcher
1913
- deleteFetcher(key);
1914
+ // Unmounted from the UI and can be totally removed
1915
+ deletedFetchersKeys.push(key);
1914
1916
  } else {
1915
- // Otherwise, it must still be mounted in the UI so we just remove
1916
- // it from state now that we've handed off the data to the React
1917
- // layer. Things such as fetchLoadMatches remain for revalidation.
1918
- state.fetchers.delete(key);
1917
+ // Returned to idle but still mounted in the UI, so semi-remains for
1918
+ // revalidations and such
1919
+ completedFetchers.push(key);
1919
1920
  }
1920
1921
  }
1921
1922
  });
1922
1923
  }
1924
+ subscribers.forEach(subscriber => subscriber(state, {
1925
+ deletedFetchers: deletedFetchersKeys,
1926
+ unstable_viewTransitionOpts: viewTransitionOpts
1927
+ }));
1928
+
1929
+ // Remove idle fetchers from state since we only care about in-flight fetchers.
1930
+ if (future.v7_fetcherPersist) {
1931
+ completedFetchers.forEach(key => state.fetchers.delete(key));
1932
+ deletedFetchersKeys.forEach(key => deleteFetcher(key));
1933
+ }
1923
1934
  }
1924
1935
 
1925
1936
  // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
@@ -2472,6 +2483,14 @@ function createRouter(init) {
2472
2483
  } : {});
2473
2484
  }
2474
2485
  function getFetcher(key) {
2486
+ if (future.v7_fetcherPersist) {
2487
+ activeFetchers.set(key, (activeFetchers.get(key) || 0) + 1);
2488
+ // If this fetcher was previously marked for deletion, unmark it since we
2489
+ // have a new instance
2490
+ if (deletedFetchers.has(key)) {
2491
+ deletedFetchers.delete(key);
2492
+ }
2493
+ }
2475
2494
  return state.fetchers.get(key) || IDLE_FETCHER;
2476
2495
  }
2477
2496
 
@@ -2545,13 +2564,20 @@ function createRouter(init) {
2545
2564
  let originatingLoadId = incrementingLoadId;
2546
2565
  let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);
2547
2566
  if (fetchRequest.signal.aborted) {
2548
- // We can delete this so long as we weren't aborted by ou our own fetcher
2567
+ // We can delete this so long as we weren't aborted by our own fetcher
2549
2568
  // re-submit which would have put _new_ controller is in fetchControllers
2550
2569
  if (fetchControllers.get(key) === abortController) {
2551
2570
  fetchControllers.delete(key);
2552
2571
  }
2553
2572
  return;
2554
2573
  }
2574
+ if (deletedFetchers.has(key)) {
2575
+ state.fetchers.set(key, getDoneFetcher(undefined));
2576
+ updateState({
2577
+ fetchers: new Map(state.fetchers)
2578
+ });
2579
+ return;
2580
+ }
2555
2581
  if (isRedirectResult(actionResult)) {
2556
2582
  fetchControllers.delete(key);
2557
2583
  if (pendingNavigationLoadId > originatingLoadId) {
@@ -2721,6 +2747,13 @@ function createRouter(init) {
2721
2747
  if (fetchRequest.signal.aborted) {
2722
2748
  return;
2723
2749
  }
2750
+ if (deletedFetchers.has(key)) {
2751
+ state.fetchers.set(key, getDoneFetcher(undefined));
2752
+ updateState({
2753
+ fetchers: new Map(state.fetchers)
2754
+ });
2755
+ return;
2756
+ }
2724
2757
 
2725
2758
  // If the loader threw a redirect Response, start a new REPLACE navigation
2726
2759
  if (isRedirectResult(result)) {
@@ -2742,17 +2775,7 @@ function createRouter(init) {
2742
2775
 
2743
2776
  // Process any non-redirect errors thrown
2744
2777
  if (isErrorResult(result)) {
2745
- let boundaryMatch = findNearestBoundary(state.matches, routeId);
2746
- state.fetchers.delete(key);
2747
- // TODO: In remix, this would reset to IDLE_NAVIGATION if it was a catch -
2748
- // do we need to behave any differently with our non-redirect errors?
2749
- // What if it was a non-redirect Response?
2750
- updateState({
2751
- fetchers: new Map(state.fetchers),
2752
- errors: {
2753
- [boundaryMatch.route.id]: result.error
2754
- }
2755
- });
2778
+ setFetcherError(key, routeId, result.error);
2756
2779
  return;
2757
2780
  }
2758
2781
  invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
@@ -2929,7 +2952,13 @@ function createRouter(init) {
2929
2952
  }
2930
2953
  function deleteFetcherAndUpdateState(key) {
2931
2954
  if (future.v7_fetcherPersist) {
2932
- deletedFetchers.add(key);
2955
+ let count = (activeFetchers.get(key) || 0) - 1;
2956
+ if (count <= 0) {
2957
+ activeFetchers.delete(key);
2958
+ deletedFetchers.add(key);
2959
+ } else {
2960
+ activeFetchers.set(key, count);
2961
+ }
2933
2962
  } else {
2934
2963
  deleteFetcher(key);
2935
2964
  }