@samitouri / QOS-React / commits / bdd3d0807c

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;