@remix-run/router 0.0.0-experimental-a077dc2d → 0.0.0-experimental-e192105b

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,27 @@
1
1
  # `@remix-run/router`
2
2
 
3
+ ## 1.11.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Add a new `future.v7_fetcherPersist` flag to the `@remix-run/router` to change the persistence behavior of fetchers when `router.deleteFetcher` is called. Instead of being immediately cleaned up, fetchers will persist until they return to an `idle` state ([RFC](https://github.com/remix-run/remix/discussions/7698)) ([#10962](https://github.com/remix-run/react-router/pull/10962))
8
+
9
+ - This is sort of a long-standing bug fix as the `useFetchers()` API was always supposed to only reflect **in-flight** fetcher information for pending/optimistic UI -- it was not intended to reflect fetcher data or hang onto fetchers after they returned to an `idle` state
10
+ - Keep an eye out for the following specific behavioral changes when opting into this flag and check your app for compatibility:
11
+ - Fetchers that complete _while still mounted_ will no longer appear in `useFetchers()`. They served effectively no purpose in there since you can access the data via `useFetcher().data`).
12
+ - Fetchers that previously unmounted _while in-flight_ will not be immediately aborted and will instead be cleaned up once they return to an `idle` state. They will remain exposed via `useFetchers` while in-flight so you can still access pending/optimistic data after unmount.
13
+
14
+ - 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))
15
+
16
+ - Once a fetcher has been totally unmounted, we can ignore post-processing of a persisted fetcher result such as a redirect or an error
17
+ - The router will also pass a new `deletedFetchers` array to the subscriber callbacks so that the UI layer can remove associated fetcher data
18
+
19
+ - Add support for optional path segments in `matchPath` ([#10768](https://github.com/remix-run/react-router/pull/10768))
20
+
21
+ ### Patch Changes
22
+
23
+ - Fix `router.getFetcher`/`router.deleteFetcher` type definitions which incorrectly specified `key` as an optional parameter ([#10960](https://github.com/remix-run/react-router/pull/10960))
24
+
3
25
  ## 1.10.0
4
26
 
5
27
  ### Minor Changes
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @remix-run/router v0.0.0-experimental-a077dc2d
2
+ * @remix-run/router v0.0.0-experimental-e192105b
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -1739,6 +1739,9 @@ function createRouter(init) {
1739
1739
  // Should the current navigation enable document.startViewTransition?
1740
1740
  let pendingViewTransitionEnabled = false;
1741
1741
 
1742
+ // Should the current navigation use flushSync for state updates?
1743
+ let pendingFlushSync = false;
1744
+
1742
1745
  // Store applied view transitions so we can apply them on POP
1743
1746
  let appliedViewTransitions = new Map();
1744
1747
 
@@ -1783,6 +1786,9 @@ function createRouter(init) {
1783
1786
  // Most recent href/match for fetcher.load calls for fetchers
1784
1787
  let fetchLoadMatches = new Map();
1785
1788
 
1789
+ // Ref-count mounted fetchers so we know when it's ok to clean them up
1790
+ let activeFetchers = new Map();
1791
+
1786
1792
  // Fetchers that have requested a delete when using v7_fetcherPersist,
1787
1793
  // they'll be officially removed after they return to idle
1788
1794
  let deletedFetchers = new Set();
@@ -1849,6 +1855,8 @@ function createRouter(init) {
1849
1855
  blockers.set(blockerKey, IDLE_BLOCKER);
1850
1856
  updateState({
1851
1857
  blockers
1858
+ }, {
1859
+ flushSync: false
1852
1860
  });
1853
1861
  }
1854
1862
  });
@@ -1897,29 +1905,42 @@ function createRouter(init) {
1897
1905
  }
1898
1906
 
1899
1907
  // Update our state and notify the calling context of the change
1900
- function updateState(newState, viewTransitionOpts) {
1908
+ function updateState(newState, opts) {
1901
1909
  state = _extends({}, state, newState);
1902
- subscribers.forEach(subscriber => subscriber(state, {
1903
- unstable_viewTransitionOpts: viewTransitionOpts
1904
- }));
1905
1910
 
1906
- // Remove idle fetchers from state since we only care about in-flight fetchers.
1911
+ // Prep fetcher cleanup so we can tell the UI which fetcher data entries
1912
+ // can be removed
1913
+ let completedFetchers = [];
1914
+ let deletedFetchersKeys = [];
1907
1915
  if (future.v7_fetcherPersist) {
1908
1916
  state.fetchers.forEach((fetcher, key) => {
1909
1917
  if (fetcher.state === "idle") {
1910
1918
  if (deletedFetchers.has(key)) {
1911
- // If the fetcher has unmounted and called router.deleteFetcher(),
1912
- // we can totally delete the fetcher
1913
- deleteFetcher(key);
1919
+ // Unmounted from the UI and can be totally removed
1920
+ deletedFetchersKeys.push(key);
1914
1921
  } 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);
1922
+ // Returned to idle but still mounted in the UI, so semi-remains for
1923
+ // revalidations and such
1924
+ completedFetchers.push(key);
1919
1925
  }
1920
1926
  }
1921
1927
  });
1922
1928
  }
1929
+
1930
+ // Iterate over a local copy so that if flushSync is used and we end up
1931
+ // removing and adding a new subscriber due to the useCallback dependencies,
1932
+ // we don't get ourselves into a loop calling the new subscriber immediately
1933
+ [...subscribers].forEach(subscriber => subscriber(state, {
1934
+ deletedFetchers: deletedFetchersKeys,
1935
+ unstable_viewTransitionOpts: opts.viewTransitionOpts,
1936
+ unstable_flushSync: opts.flushSync
1937
+ }));
1938
+
1939
+ // Remove idle fetchers from state since we only care about in-flight fetchers.
1940
+ if (future.v7_fetcherPersist) {
1941
+ completedFetchers.forEach(key => state.fetchers.delete(key));
1942
+ deletedFetchersKeys.forEach(key => deleteFetcher(key));
1943
+ }
1923
1944
  }
1924
1945
 
1925
1946
  // Complete a navigation returning the state.navigation back to the IDLE_NAVIGATION
@@ -2019,12 +2040,16 @@ function createRouter(init) {
2019
2040
  restoreScrollPosition: getSavedScrollPosition(location, newState.matches || state.matches),
2020
2041
  preventScrollReset,
2021
2042
  blockers
2022
- }), viewTransitionOpts);
2043
+ }), {
2044
+ viewTransitionOpts,
2045
+ flushSync: pendingFlushSync
2046
+ });
2023
2047
 
2024
2048
  // Reset stateful navigation vars
2025
2049
  pendingAction = Action.Pop;
2026
2050
  pendingPreventScrollReset = false;
2027
2051
  pendingViewTransitionEnabled = false;
2052
+ pendingFlushSync = false;
2028
2053
  isUninterruptedRevalidation = false;
2029
2054
  isRevalidationRequired = false;
2030
2055
  cancelledDeferredRoutes = [];
@@ -2065,6 +2090,7 @@ function createRouter(init) {
2065
2090
  historyAction = Action.Replace;
2066
2091
  }
2067
2092
  let preventScrollReset = opts && "preventScrollReset" in opts ? opts.preventScrollReset === true : undefined;
2093
+ let flushSync = (opts && opts.unstable_flushSync) === true;
2068
2094
  let blockerKey = shouldBlockNavigation({
2069
2095
  currentLocation,
2070
2096
  nextLocation,
@@ -2090,6 +2116,8 @@ function createRouter(init) {
2090
2116
  blockers.set(blockerKey, IDLE_BLOCKER);
2091
2117
  updateState({
2092
2118
  blockers
2119
+ }, {
2120
+ flushSync
2093
2121
  });
2094
2122
  }
2095
2123
  });
@@ -2102,7 +2130,8 @@ function createRouter(init) {
2102
2130
  pendingError: error,
2103
2131
  preventScrollReset,
2104
2132
  replace: opts && opts.replace,
2105
- enableViewTransition: opts && opts.unstable_viewTransition
2133
+ enableViewTransition: opts && opts.unstable_viewTransition,
2134
+ flushSync
2106
2135
  });
2107
2136
  }
2108
2137
 
@@ -2113,6 +2142,8 @@ function createRouter(init) {
2113
2142
  interruptActiveLoads();
2114
2143
  updateState({
2115
2144
  revalidation: "loading"
2145
+ }, {
2146
+ flushSync: false
2116
2147
  });
2117
2148
 
2118
2149
  // If we're currently submitting an action, we don't need to start a new
@@ -2156,6 +2187,7 @@ function createRouter(init) {
2156
2187
  saveScrollPosition(state.location, state.matches);
2157
2188
  pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2158
2189
  pendingViewTransitionEnabled = (opts && opts.enableViewTransition) === true;
2190
+ pendingFlushSync = (opts && opts.flushSync) === true;
2159
2191
  let routesToUse = inFlightDataRoutes || dataRoutes;
2160
2192
  let loadingNavigation = opts && opts.overrideNavigation;
2161
2193
  let matches = matchRoutes(routesToUse, location, basename);
@@ -2261,6 +2293,8 @@ function createRouter(init) {
2261
2293
  let navigation = getSubmittingNavigation(location, submission);
2262
2294
  updateState({
2263
2295
  navigation
2296
+ }, {
2297
+ flushSync: pendingFlushSync
2264
2298
  });
2265
2299
 
2266
2300
  // Call our action and get the result
@@ -2388,7 +2422,9 @@ function createRouter(init) {
2388
2422
  actionData
2389
2423
  } : {}, revalidatingFetchers.length > 0 ? {
2390
2424
  fetchers: new Map(state.fetchers)
2391
- } : {}));
2425
+ } : {}), {
2426
+ flushSync: pendingFlushSync
2427
+ });
2392
2428
  }
2393
2429
  revalidatingFetchers.forEach(rf => {
2394
2430
  if (fetchControllers.has(rf.key)) {
@@ -2471,9 +2507,6 @@ function createRouter(init) {
2471
2507
  fetchers: new Map(state.fetchers)
2472
2508
  } : {});
2473
2509
  }
2474
- function getFetcher(key) {
2475
- return state.fetchers.get(key) || IDLE_FETCHER;
2476
- }
2477
2510
 
2478
2511
  // Trigger a fetcher load/submit for the given fetcher key
2479
2512
  function fetch(key, routeId, href, opts) {
@@ -2481,13 +2514,16 @@ function createRouter(init) {
2481
2514
  throw new Error("router.fetch() was called during the server render, but it shouldn't be. " + "You are likely calling a useFetcher() method in the body of your component. " + "Try moving it to a useEffect or a callback.");
2482
2515
  }
2483
2516
  if (fetchControllers.has(key)) abortFetcher(key);
2517
+ let flushSync = (opts && opts.unstable_flushSync) === true;
2484
2518
  let routesToUse = inFlightDataRoutes || dataRoutes;
2485
2519
  let normalizedPath = normalizeTo(state.location, state.matches, basename, future.v7_prependBasename, href, routeId, opts == null ? void 0 : opts.relative);
2486
2520
  let matches = matchRoutes(routesToUse, normalizedPath, basename);
2487
2521
  if (!matches) {
2488
2522
  setFetcherError(key, routeId, getInternalRouterError(404, {
2489
2523
  pathname: normalizedPath
2490
- }));
2524
+ }), {
2525
+ flushSync
2526
+ });
2491
2527
  return;
2492
2528
  }
2493
2529
  let {
@@ -2496,13 +2532,15 @@ function createRouter(init) {
2496
2532
  error
2497
2533
  } = normalizeNavigateOptions(future.v7_normalizeFormMethod, true, normalizedPath, opts);
2498
2534
  if (error) {
2499
- setFetcherError(key, routeId, error);
2535
+ setFetcherError(key, routeId, error, {
2536
+ flushSync
2537
+ });
2500
2538
  return;
2501
2539
  }
2502
2540
  let match = getTargetMatch(matches, path);
2503
2541
  pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
2504
2542
  if (submission && isMutationMethod(submission.formMethod)) {
2505
- handleFetcherAction(key, routeId, path, match, matches, submission);
2543
+ handleFetcherAction(key, routeId, path, match, matches, flushSync, submission);
2506
2544
  return;
2507
2545
  }
2508
2546
 
@@ -2512,12 +2550,12 @@ function createRouter(init) {
2512
2550
  routeId,
2513
2551
  path
2514
2552
  });
2515
- handleFetcherLoader(key, routeId, path, match, matches, submission);
2553
+ handleFetcherLoader(key, routeId, path, match, matches, flushSync, submission);
2516
2554
  }
2517
2555
 
2518
2556
  // Call the action for the matched fetcher.submit(), and then handle redirects,
2519
2557
  // errors, and revalidation
2520
- async function handleFetcherAction(key, routeId, path, match, requestMatches, submission) {
2558
+ async function handleFetcherAction(key, routeId, path, match, requestMatches, flushSync, submission) {
2521
2559
  interruptActiveLoads();
2522
2560
  fetchLoadMatches.delete(key);
2523
2561
  if (!match.route.action && !match.route.lazy) {
@@ -2526,16 +2564,16 @@ function createRouter(init) {
2526
2564
  pathname: path,
2527
2565
  routeId: routeId
2528
2566
  });
2529
- setFetcherError(key, routeId, error);
2567
+ setFetcherError(key, routeId, error, {
2568
+ flushSync
2569
+ });
2530
2570
  return;
2531
2571
  }
2532
2572
 
2533
2573
  // Put this fetcher into it's submitting state
2534
2574
  let existingFetcher = state.fetchers.get(key);
2535
- let fetcher = getSubmittingFetcher(submission, existingFetcher);
2536
- state.fetchers.set(key, fetcher);
2537
- updateState({
2538
- fetchers: new Map(state.fetchers)
2575
+ updateFetcherState(key, getSubmittingFetcher(submission, existingFetcher), {
2576
+ flushSync
2539
2577
  });
2540
2578
 
2541
2579
  // Call the action for the fetcher
@@ -2545,13 +2583,19 @@ function createRouter(init) {
2545
2583
  let originatingLoadId = incrementingLoadId;
2546
2584
  let actionResult = await callLoaderOrAction("action", fetchRequest, match, requestMatches, manifest, mapRouteProperties, basename);
2547
2585
  if (fetchRequest.signal.aborted) {
2548
- // We can delete this so long as we weren't aborted by ou our own fetcher
2586
+ // We can delete this so long as we weren't aborted by our own fetcher
2549
2587
  // re-submit which would have put _new_ controller is in fetchControllers
2550
2588
  if (fetchControllers.get(key) === abortController) {
2551
2589
  fetchControllers.delete(key);
2552
2590
  }
2553
2591
  return;
2554
2592
  }
2593
+ if (deletedFetchers.has(key)) {
2594
+ updateFetcherState(key, getDoneFetcher(undefined), {
2595
+ flushSync
2596
+ });
2597
+ return;
2598
+ }
2555
2599
  if (isRedirectResult(actionResult)) {
2556
2600
  fetchControllers.delete(key);
2557
2601
  if (pendingNavigationLoadId > originatingLoadId) {
@@ -2559,18 +2603,14 @@ function createRouter(init) {
2559
2603
  // should take precedence over this redirect navigation. We already
2560
2604
  // set isRevalidationRequired so all loaders for the new route should
2561
2605
  // fire unless opted out via shouldRevalidate
2562
- let doneFetcher = getDoneFetcher(undefined);
2563
- state.fetchers.set(key, doneFetcher);
2564
- updateState({
2565
- fetchers: new Map(state.fetchers)
2606
+ updateFetcherState(key, getDoneFetcher(undefined), {
2607
+ flushSync
2566
2608
  });
2567
2609
  return;
2568
2610
  } else {
2569
2611
  fetchRedirectIds.add(key);
2570
- let loadingFetcher = getLoadingFetcher(submission);
2571
- state.fetchers.set(key, loadingFetcher);
2572
- updateState({
2573
- fetchers: new Map(state.fetchers)
2612
+ updateFetcherState(key, getLoadingFetcher(submission), {
2613
+ flushSync
2574
2614
  });
2575
2615
  return startRedirectNavigation(state, actionResult, {
2576
2616
  fetcherSubmission: submission
@@ -2580,7 +2620,9 @@ function createRouter(init) {
2580
2620
 
2581
2621
  // Process any non-redirect errors thrown
2582
2622
  if (isErrorResult(actionResult)) {
2583
- setFetcherError(key, routeId, actionResult.error);
2623
+ setFetcherError(key, routeId, actionResult.error, {
2624
+ flushSync
2625
+ });
2584
2626
  return;
2585
2627
  }
2586
2628
  if (isDeferredResult(actionResult)) {
@@ -2622,6 +2664,8 @@ function createRouter(init) {
2622
2664
  });
2623
2665
  updateState({
2624
2666
  fetchers: new Map(state.fetchers)
2667
+ }, {
2668
+ flushSync
2625
2669
  });
2626
2670
  let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach(rf => abortFetcher(rf.key));
2627
2671
  abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
@@ -2683,19 +2727,18 @@ function createRouter(init) {
2683
2727
  errors,
2684
2728
  loaderData: mergeLoaderData(state.loaderData, loaderData, matches, errors),
2685
2729
  fetchers: new Map(state.fetchers)
2730
+ }, {
2731
+ flushSync
2686
2732
  });
2687
2733
  isRevalidationRequired = false;
2688
2734
  }
2689
2735
  }
2690
2736
 
2691
2737
  // Call the matched loader for fetcher.load(), handling redirects, errors, etc.
2692
- async function handleFetcherLoader(key, routeId, path, match, matches, submission) {
2738
+ async function handleFetcherLoader(key, routeId, path, match, matches, flushSync, submission) {
2693
2739
  let existingFetcher = state.fetchers.get(key);
2694
- // Put this fetcher into it's loading state
2695
- let loadingFetcher = getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined);
2696
- state.fetchers.set(key, loadingFetcher);
2697
- updateState({
2698
- fetchers: new Map(state.fetchers)
2740
+ updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : undefined), {
2741
+ flushSync
2699
2742
  });
2700
2743
 
2701
2744
  // Call the loader for this fetcher route match
@@ -2721,16 +2764,20 @@ function createRouter(init) {
2721
2764
  if (fetchRequest.signal.aborted) {
2722
2765
  return;
2723
2766
  }
2767
+ if (deletedFetchers.has(key)) {
2768
+ updateFetcherState(key, getDoneFetcher(undefined), {
2769
+ flushSync
2770
+ });
2771
+ return;
2772
+ }
2724
2773
 
2725
2774
  // If the loader threw a redirect Response, start a new REPLACE navigation
2726
2775
  if (isRedirectResult(result)) {
2727
2776
  if (pendingNavigationLoadId > originatingLoadId) {
2728
2777
  // A new navigation was kicked off after our loader started, so that
2729
2778
  // should take precedence over this redirect navigation
2730
- let doneFetcher = getDoneFetcher(undefined);
2731
- state.fetchers.set(key, doneFetcher);
2732
- updateState({
2733
- fetchers: new Map(state.fetchers)
2779
+ updateFetcherState(key, getDoneFetcher(undefined), {
2780
+ flushSync
2734
2781
  });
2735
2782
  return;
2736
2783
  } else {
@@ -2742,26 +2789,16 @@ function createRouter(init) {
2742
2789
 
2743
2790
  // Process any non-redirect errors thrown
2744
2791
  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
- }
2792
+ setFetcherError(key, routeId, result.error, {
2793
+ flushSync
2755
2794
  });
2756
2795
  return;
2757
2796
  }
2758
2797
  invariant(!isDeferredResult(result), "Unhandled fetcher deferred data");
2759
2798
 
2760
2799
  // Put the fetcher back into an idle state
2761
- let doneFetcher = getDoneFetcher(result.data);
2762
- state.fetchers.set(key, doneFetcher);
2763
- updateState({
2764
- fetchers: new Map(state.fetchers)
2800
+ updateFetcherState(key, getDoneFetcher(result.data), {
2801
+ flushSync
2765
2802
  });
2766
2803
  }
2767
2804
 
@@ -2903,7 +2940,15 @@ function createRouter(init) {
2903
2940
  }
2904
2941
  });
2905
2942
  }
2906
- function setFetcherError(key, routeId, error) {
2943
+ function updateFetcherState(key, fetcher, opts) {
2944
+ state.fetchers.set(key, fetcher);
2945
+ updateState({
2946
+ fetchers: new Map(state.fetchers)
2947
+ }, {
2948
+ flushSync: opts.flushSync
2949
+ });
2950
+ }
2951
+ function setFetcherError(key, routeId, error, opts) {
2907
2952
  let boundaryMatch = findNearestBoundary(state.matches, routeId);
2908
2953
  deleteFetcher(key);
2909
2954
  updateState({
@@ -2911,8 +2956,21 @@ function createRouter(init) {
2911
2956
  [boundaryMatch.route.id]: error
2912
2957
  },
2913
2958
  fetchers: new Map(state.fetchers)
2959
+ }, {
2960
+ flushSync: opts.flushSync
2914
2961
  });
2915
2962
  }
2963
+ function getFetcher(key) {
2964
+ if (future.v7_fetcherPersist) {
2965
+ activeFetchers.set(key, (activeFetchers.get(key) || 0) + 1);
2966
+ // If this fetcher was previously marked for deletion, unmark it since we
2967
+ // have a new instance
2968
+ if (deletedFetchers.has(key)) {
2969
+ deletedFetchers.delete(key);
2970
+ }
2971
+ }
2972
+ return state.fetchers.get(key) || IDLE_FETCHER;
2973
+ }
2916
2974
  function deleteFetcher(key) {
2917
2975
  let fetcher = state.fetchers.get(key);
2918
2976
  // Don't abort the controller if this is a deletion of a fetcher.submit()
@@ -2929,12 +2987,20 @@ function createRouter(init) {
2929
2987
  }
2930
2988
  function deleteFetcherAndUpdateState(key) {
2931
2989
  if (future.v7_fetcherPersist) {
2932
- deletedFetchers.add(key);
2990
+ let count = (activeFetchers.get(key) || 0) - 1;
2991
+ if (count <= 0) {
2992
+ activeFetchers.delete(key);
2993
+ deletedFetchers.add(key);
2994
+ } else {
2995
+ activeFetchers.set(key, count);
2996
+ }
2933
2997
  } else {
2934
2998
  deleteFetcher(key);
2935
2999
  }
2936
3000
  updateState({
2937
3001
  fetchers: new Map(state.fetchers)
3002
+ }, {
3003
+ flushSync: false
2938
3004
  });
2939
3005
  }
2940
3006
  function abortFetcher(key) {
@@ -3004,6 +3070,8 @@ function createRouter(init) {
3004
3070
  blockers.set(key, newBlocker);
3005
3071
  updateState({
3006
3072
  blockers
3073
+ }, {
3074
+ flushSync: false
3007
3075
  });
3008
3076
  }
3009
3077
  function shouldBlockNavigation(_ref2) {
@@ -3071,6 +3139,8 @@ function createRouter(init) {
3071
3139
  if (y != null) {
3072
3140
  updateState({
3073
3141
  restoreScrollPosition: y
3142
+ }, {
3143
+ flushSync: false
3074
3144
  });
3075
3145
  }
3076
3146
  }