Extract logic for detecting bad fallback to helper
Pure refactor, no change in behavior. Extracts the logic for detecting whether a suspended component will result in a "bad" Suspense fallback into a helper function. An example of a bad Suspense fallback is one that causes already-visible content to disappear. I want to reuse this same logic in the work loop, too.
Andrew Clark committed
Oct 24, 2022 at 10:47 UTC
bdd3d0807c6e9f417909bab93ffe9795128b04d8
6 files changed
+70
-40
packages/react-reconciler/src/ReactFiberCompleteWork.new.js
+3
-19
@@ -31,7 +31,6 @@ import type {OffscreenState} from './ReactFiberOffscreenComponent';
31
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.new';
32
import type {Cache} from './ReactFiberCacheComponent.new';
33
import {
34
- enableSuspenseAvoidThisFallback,
34
enableLegacyHidden,
35
enableHostSingletons,
36
enableSuspenseCallback,
@@ -127,11 +126,9 @@ import {
126
setShallowSuspenseListContext,
127
ForceSuspenseFallback,
128
setDefaultShallowSuspenseListContext,
129
+ isBadSuspenseFallback,
130
} from './ReactFiberSuspenseContext.new';
131
-import {
132
- popHiddenContext,
133
- isCurrentTreeHidden,
134
-} from './ReactFiberHiddenContext.new';
131
+import {popHiddenContext} from './ReactFiberHiddenContext.new';
132
import {findFirstSuspended} from './ReactFiberSuspenseComponent.new';
133
import {
134
isContextProvider as isLegacyContextProvider,
@@ -1272,20 +1269,7 @@ function completeWork(
1269
// If this render already had a ping or lower pri updates,
1270
// and this is the first time we know we're going to suspend we
1271
// should be able to immediately restart from within throwException.
1275
-
1276
- // Check if this is a "bad" fallback state or a good one. A bad
1277
- // fallback state is one that we only show as a last resort; if this
1278
- // is a transition, we'll block it from displaying, and wait for
1279
- // more data to arrive.
1280
- const isBadFallback =
1281
- // It's bad to switch to a fallback if content is already visible
1282
- (current !== null && !prevDidTimeout && !isCurrentTreeHidden()) ||
1283
- // Experimental: Some fallbacks are always bad
1284
- (enableSuspenseAvoidThisFallback &&
1285
- workInProgress.memoizedProps.unstable_avoidThisFallback ===
1286
- true);
1287
-
1288
- if (isBadFallback) {
1272
+ if (isBadSuspenseFallback(current, newProps)) {
1273
renderDidSuspendDelayIfPossible();
1274
} else {
1275
renderDidSuspend();
packages/react-reconciler/src/ReactFiberCompleteWork.old.js
+3
-19
@@ -31,7 +31,6 @@ import type {OffscreenState} from './ReactFiberOffscreenComponent';
31
import type {TracingMarkerInstance} from './ReactFiberTracingMarkerComponent.old';
32
import type {Cache} from './ReactFiberCacheComponent.old';
33
import {
34
- enableSuspenseAvoidThisFallback,
34
enableLegacyHidden,
35
enableHostSingletons,
36
enableSuspenseCallback,
@@ -127,11 +126,9 @@ import {
126
setShallowSuspenseListContext,
127
ForceSuspenseFallback,
128
setDefaultShallowSuspenseListContext,
129
+ isBadSuspenseFallback,
130
} from './ReactFiberSuspenseContext.old';
131
-import {
132
- popHiddenContext,
133
- isCurrentTreeHidden,
134
-} from './ReactFiberHiddenContext.old';
131
+import {popHiddenContext} from './ReactFiberHiddenContext.old';
132
import {findFirstSuspended} from './ReactFiberSuspenseComponent.old';
133
import {
134
isContextProvider as isLegacyContextProvider,
@@ -1272,20 +1269,7 @@ function completeWork(
1269
// If this render already had a ping or lower pri updates,
1270
// and this is the first time we know we're going to suspend we
1271
// should be able to immediately restart from within throwException.
1275
-
1276
- // Check if this is a "bad" fallback state or a good one. A bad
1277
- // fallback state is one that we only show as a last resort; if this
1278
- // is a transition, we'll block it from displaying, and wait for
1279
- // more data to arrive.
1280
- const isBadFallback =
1281
- // It's bad to switch to a fallback if content is already visible
1282
- (current !== null && !prevDidTimeout && !isCurrentTreeHidden()) ||
1283
- // Experimental: Some fallbacks are always bad
1284
- (enableSuspenseAvoidThisFallback &&
1285
- workInProgress.memoizedProps.unstable_avoidThisFallback ===
1286
- true);
1287
-
1288
- if (isBadFallback) {
1272
+ if (isBadSuspenseFallback(current, newProps)) {
1273
renderDidSuspendDelayIfPossible();
1274
} else {
1275
renderDidSuspend();
packages/react-reconciler/src/ReactFiberSuspenseComponent.new.js
+1
@@ -27,6 +27,7 @@ export type SuspenseProps = {
27
// TODO: Add "unstable_" prefix?
28
suspenseCallback?: (Set<Wakeable> | null) => mixed,
29
30
+ unstable_avoidThisFallback?: boolean,
31
unstable_expectedLoadTime?: number,
32
unstable_name?: string,
33
};
packages/react-reconciler/src/ReactFiberSuspenseComponent.old.js
+1
@@ -27,6 +27,7 @@ export type SuspenseProps = {
27
// TODO: Add "unstable_" prefix?
28
suspenseCallback?: (Set<Wakeable> | null) => mixed,
29
30
+ unstable_avoidThisFallback?: boolean,
31
unstable_expectedLoadTime?: number,
32
unstable_name?: string,
33
};
packages/react-reconciler/src/ReactFiberSuspenseContext.new.js
+31
-1
@@ -9,7 +9,10 @@
9
10
import type {Fiber} from './ReactInternalTypes';
11
import type {StackCursor} from './ReactFiberStack.new';
12
-import type {SuspenseState} from './ReactFiberSuspenseComponent.new';
12
+import type {
13
+ SuspenseState,
14
+ SuspenseProps,
15
+} from './ReactFiberSuspenseComponent.new';
16
17
import {enableSuspenseAvoidThisFallback} from 'shared/ReactFeatureFlags';
18
import {createCursor, push, pop} from './ReactFiberStack.new';
@@ -55,6 +58,33 @@ function shouldAvoidedBoundaryCapture(
58
return false;
59
}
60
61
+export function isBadSuspenseFallback(
62
+ current: Fiber | null,
63
+ nextProps: SuspenseProps,
64
+): boolean {
65
+ // Check if this is a "bad" fallback state or a good one. A bad fallback state
66
+ // is one that we only show as a last resort; if this is a transition, we'll
67
+ // block it from displaying, and wait for more data to arrive.
68
+ if (current !== null) {
69
+ const prevState: SuspenseState = current.memoizedState;
70
+ const isShowingFallback = prevState !== null;
71
+ if (!isShowingFallback && !isCurrentTreeHidden()) {
72
+ // It's bad to switch to a fallback if content is already visible
73
+ return true;
74
+ }
75
+ }
76
+
77
+ if (
78
+ enableSuspenseAvoidThisFallback &&
79
+ nextProps.unstable_avoidThisFallback === true
80
+ ) {
81
+ // Experimental: Some fallbacks are always bad
82
+ return true;
83
+ }
84
+
85
+ return false;
86
+}
87
+
88
export function pushPrimaryTreeSuspenseHandler(handler: Fiber): void {
89
const props = handler.pendingProps;
90
const handlerOnStack = suspenseHandlerStackCursor.current;
packages/react-reconciler/src/ReactFiberSuspenseContext.old.js
+31
-1
@@ -9,7 +9,10 @@
9
10
import type {Fiber} from './ReactInternalTypes';
11
import type {StackCursor} from './ReactFiberStack.old';
12
-import type {SuspenseState} from './ReactFiberSuspenseComponent.old';
12
+import type {
13
+ SuspenseState,
14
+ SuspenseProps,
15
+} from './ReactFiberSuspenseComponent.old';
16
17
import {enableSuspenseAvoidThisFallback} from 'shared/ReactFeatureFlags';
18
import {createCursor, push, pop} from './ReactFiberStack.old';
@@ -55,6 +58,33 @@ function shouldAvoidedBoundaryCapture(
58
return false;
59
}
60
61
+export function isBadSuspenseFallback(
62
+ current: Fiber | null,
63
+ nextProps: SuspenseProps,
64
+): boolean {
65
+ // Check if this is a "bad" fallback state or a good one. A bad fallback state
66
+ // is one that we only show as a last resort; if this is a transition, we'll
67
+ // block it from displaying, and wait for more data to arrive.
68
+ if (current !== null) {
69
+ const prevState: SuspenseState = current.memoizedState;
70
+ const isShowingFallback = prevState !== null;
71
+ if (!isShowingFallback && !isCurrentTreeHidden()) {
72
+ // It's bad to switch to a fallback if content is already visible
73
+ return true;
74
+ }
75
+ }
76
+
77
+ if (
78
+ enableSuspenseAvoidThisFallback &&
79
+ nextProps.unstable_avoidThisFallback === true
80
+ ) {
81
+ // Experimental: Some fallbacks are always bad
82
+ return true;
83
+ }
84
+
85
+ return false;
86
+}
87
+
88
export function pushPrimaryTreeSuspenseHandler(handler: Fiber): void {
89
const props = handler.pendingProps;
90
const handlerOnStack = suspenseHandlerStackCursor.current;