Add `alwaysThrottleRetries` flag (#26685)
This puts the change introduced by #26611 behind a flag until Meta is able to roll it out. Disabling the flag reverts back to the old behavior, where retries are throttled if there's still data remaining in the tree, but not if all the data has finished loading. The new behavior is still enabled in the public builds.
Andrew Clark committed
Apr 20, 2023 at 14:23 UTC
d73d7d59086218b0fa42d0a79c32a0365952650b
12 files changed
+45
-6
packages/react-reconciler/src/ReactFiberWorkLoop.js
+5
-1
@@ -39,6 +39,7 @@ import {
39
enableTransitionTracing,
40
useModernStrictMode,
41
disableLegacyContext,
42
+ alwaysThrottleRetries,
43
} from 'shared/ReactFeatureFlags';
44
import ReactSharedInternals from 'shared/ReactSharedInternals';
45
import is from 'shared/objectIs';
@@ -1115,7 +1116,10 @@ function finishConcurrentRender(
1116
workInProgressTransitions,
1117
);
1118
} else {
1118
- if (includesOnlyRetries(lanes)) {
1119
+ if (
1120
+ includesOnlyRetries(lanes) &&
1121
+ (alwaysThrottleRetries || exitStatus === RootSuspended)
1122
+ ) {
1123
// This render only included retries, no updates. Throttle committing
1124
// retries so that we don't show too many loading states too quickly.
1125
const msUntilTimeout =
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+21
-3
@@ -1779,10 +1779,28 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1779
await resolveText('B');
1780
expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
1781
1782
- // Restart and render the complete content. The tree will finish but we
1783
- // won't commit the result yet because the fallback appeared recently.
1782
+ // Restart and render the complete content.
1783
await waitForAll(['A', 'B']);
1785
- expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
1784
+
1785
+ if (gate(flags => flags.alwaysThrottleRetries)) {
1786
+ // Correct behavior:
1787
+ //
1788
+ // The tree will finish but we won't commit the result yet because the fallback appeared recently.
1789
+ expect(ReactNoop).toMatchRenderedOutput(<span prop="Loading..." />);
1790
+ } else {
1791
+ // Old behavior, gated until this rolls out at Meta:
1792
+ //
1793
+ // TODO: Because this render was the result of a retry, and a fallback
1794
+ // was shown recently, we should suspend and remain on the fallback for
1795
+ // little bit longer. We currently only do this if there's still
1796
+ // remaining fallbacks in the tree, but we should do it for all retries.
1797
+ expect(ReactNoop).toMatchRenderedOutput(
1798
+ <>
1799
+ <span prop="A" />
1800
+ <span prop="B" />
1801
+ </>,
1802
+ );
1803
+ }
1804
});
1805
assertLog([]);
1806
expect(ReactNoop).toMatchRenderedOutput(
packages/shared/ReactFeatureFlags.js
+2
@@ -124,6 +124,8 @@ export const diffInCommitPhase = __EXPERIMENTAL__;
124
125
export const enableAsyncActions = __EXPERIMENTAL__;
126
127
+export const alwaysThrottleRetries = true;
128
+
129
// -----------------------------------------------------------------------------
130
// Chopping Block
131
//
packages/shared/forks/ReactFeatureFlags.native-fb-dynamic.js
+1
@@ -22,6 +22,7 @@ import typeof * as DynamicFlagsType from 'ReactNativeInternalFeatureFlags';
22
23
export const enableUseRefAccessWarning = __VARIANT__;
24
export const enableDeferRootSchedulingToMicrotask = __VARIANT__;
25
+export const alwaysThrottleRetries = __VARIANT__;
26
27
// Flow magic to verify the exports of this file match the original version.
28
((((null: any): ExportsType): DynamicFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.native-fb.js
+5
-2
@@ -17,8 +17,11 @@ import * as dynamicFlags from 'ReactNativeInternalFeatureFlags';
17
18
// We destructure each value before re-exporting to avoid a dynamic look-up on
19
// the exports object every time a flag is read.
20
-export const {enableUseRefAccessWarning, enableDeferRootSchedulingToMicrotask} =
21
- dynamicFlags;
20
+export const {
21
+ enableUseRefAccessWarning,
22
+ enableDeferRootSchedulingToMicrotask,
23
+ alwaysThrottleRetries,
24
+} = dynamicFlags;
25
26
// The rest of the flags are static for better dead code elimination.
27
export const enableDebugTracing = false;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+2
@@ -75,5 +75,7 @@ export const enableDeferRootSchedulingToMicrotask = true;
75
export const diffInCommitPhase = true;
76
export const enableAsyncActions = false;
77
78
+export const alwaysThrottleRetries = true;
79
+
80
// Flow magic to verify the exports of this file match the original version.
81
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+2
@@ -75,5 +75,7 @@ export const enableDeferRootSchedulingToMicrotask = true;
75
export const diffInCommitPhase = true;
76
export const enableAsyncActions = false;
77
78
+export const alwaysThrottleRetries = true;
79
+
80
// Flow magic to verify the exports of this file match the original version.
81
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.test-renderer.native.js
+2
@@ -72,5 +72,7 @@ export const enableDeferRootSchedulingToMicrotask = true;
72
export const diffInCommitPhase = true;
73
export const enableAsyncActions = false;
74
75
+export const alwaysThrottleRetries = true;
76
+
77
// Flow magic to verify the exports of this file match the original version.
78
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+2
@@ -77,5 +77,7 @@ export const enableDeferRootSchedulingToMicrotask = true;
77
export const diffInCommitPhase = true;
78
export const enableAsyncActions = false;
79
80
+export const alwaysThrottleRetries = true;
81
+
82
// Flow magic to verify the exports of this file match the original version.
83
((((null: any): ExportsType): FeatureFlagsType): ExportsType);
packages/shared/forks/ReactFeatureFlags.www-dynamic.js
+1
@@ -27,6 +27,7 @@ export const enableCustomElementPropertySupport = __VARIANT__;
27
export const enableDeferRootSchedulingToMicrotask = __VARIANT__;
28
export const diffInCommitPhase = __VARIANT__;
29
export const enableAsyncActions = __VARIANT__;
30
+export const alwaysThrottleRetries = __VARIANT__;
31
32
// Enable this flag to help with concurrent mode debugging.
33
// It logs information to the console about React scheduling, rendering, and commit phases.
packages/shared/forks/ReactFeatureFlags.www.js
+1
@@ -30,6 +30,7 @@ export const {
30
enableDeferRootSchedulingToMicrotask,
31
diffInCommitPhase,
32
enableAsyncActions,
33
+ alwaysThrottleRetries,
34
} = dynamicFeatureFlags;
35
36
// On WWW, __EXPERIMENTAL__ is used for a new modern build.
scripts/flow/xplat.js
+1
@@ -10,4 +10,5 @@
10
declare module 'ReactNativeInternalFeatureFlags' {
11
declare export var enableUseRefAccessWarning: boolean;
12
declare export var enableDeferRootSchedulingToMicrotask: boolean;
13
+ declare export var alwaysThrottleRetries: boolean;
14
}