Skip to content

Commit 7c1ba2b

Browse files
author
Brian Vaughn
authored
Proposed new Suspense layout effect semantics (#21079)
This commit contains a proposed change to layout effect semantics within Suspense subtrees: If a component mounts within a Suspense boundary and is later hidden (because of something else suspending) React will cleanup that component’s layout effects (including React-managed refs). This change will hopefully fix existing bugs that occur because of things like reading layout in a hidden tree and will also enable a point at which to e.g. pause videos and hide user-managed portals. After the suspended boundary resolves, React will setup the component’s layout effects again (including React-managed refs). The scenario described above is not common. The useTransition API should ensure that Suspense does not revert to its fallback state after being mounted. Note that these changes are primarily written in terms of the (as of yet internal) Offscreen API as we intend to provide similar effects semantics within recently shown/hidden Offscreen trees in the future. (More to follow.) (Note that all changes in this PR are behind a new feature flag, enableSuspenseLayoutEffectSemantics, which is disabled for now.)
1 parent 316aa36 commit 7c1ba2b

25 files changed

+3878
-140
lines changed

packages/react-reconciler/src/ReactFiberBeginWork.new.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {
6666
DidCapture,
6767
Update,
6868
Ref,
69+
RefStatic,
6970
ChildDeletion,
7071
ForceUpdateForLegacySuspense,
7172
StaticMask,
@@ -83,6 +84,7 @@ import {
8384
enableScopeAPI,
8485
enableCache,
8586
enableLazyContextPropagation,
87+
enableSuspenseLayoutEffectSemantics,
8688
} from 'shared/ReactFeatureFlags';
8789
import invariant from 'shared/invariant';
8890
import shallowEqual from 'shared/shallowEqual';
@@ -854,6 +856,9 @@ function markRef(current: Fiber | null, workInProgress: Fiber) {
854856
) {
855857
// Schedule a Ref effect
856858
workInProgress.flags |= Ref;
859+
if (enableSuspenseLayoutEffectSemantics) {
860+
workInProgress.flags |= RefStatic;
861+
}
857862
}
858863
}
859864

packages/react-reconciler/src/ReactFiberBeginWork.old.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ import {
6666
DidCapture,
6767
Update,
6868
Ref,
69+
RefStatic,
6970
ChildDeletion,
7071
ForceUpdateForLegacySuspense,
7172
StaticMask,
@@ -83,6 +84,7 @@ import {
8384
enableScopeAPI,
8485
enableCache,
8586
enableLazyContextPropagation,
87+
enableSuspenseLayoutEffectSemantics,
8688
} from 'shared/ReactFeatureFlags';
8789
import invariant from 'shared/invariant';
8890
import shallowEqual from 'shared/shallowEqual';
@@ -854,6 +856,9 @@ function markRef(current: Fiber | null, workInProgress: Fiber) {
854856
) {
855857
// Schedule a Ref effect
856858
workInProgress.flags |= Ref;
859+
if (enableSuspenseLayoutEffectSemantics) {
860+
workInProgress.flags |= RefStatic;
861+
}
857862
}
858863
}
859864

packages/react-reconciler/src/ReactFiberClassComponent.new.js

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,15 @@
1010
import type {Fiber} from './ReactInternalTypes';
1111
import type {Lanes} from './ReactFiberLane.new';
1212
import type {UpdateQueue} from './ReactUpdateQueue.new';
13+
import type {Flags} from './ReactFiberFlags';
1314

1415
import * as React from 'react';
15-
import {MountLayoutDev, Update, Snapshot} from './ReactFiberFlags';
16+
import {
17+
LayoutStatic,
18+
MountLayoutDev,
19+
Update,
20+
Snapshot,
21+
} from './ReactFiberFlags';
1622
import {
1723
debugRenderPhaseSideEffectsForStrictMode,
1824
disableLegacyContext,
@@ -21,6 +27,7 @@ import {
2127
warnAboutDeprecatedLifecycles,
2228
enableStrictEffects,
2329
enableLazyContextPropagation,
30+
enableSuspenseLayoutEffectSemantics,
2431
} from 'shared/ReactFeatureFlags';
2532
import ReactStrictModeWarnings from './ReactStrictModeWarnings.new';
2633
import {isMounted} from './ReactFiberTreeReflection';
@@ -908,16 +915,19 @@ function mountClassInstance(
908915
}
909916

910917
if (typeof instance.componentDidMount === 'function') {
918+
let fiberFlags: Flags = Update;
919+
if (enableSuspenseLayoutEffectSemantics) {
920+
fiberFlags |= LayoutStatic;
921+
}
911922
if (
912923
__DEV__ &&
913924
enableStrictEffects &&
914925
(workInProgress.mode & StrictEffectsMode) !== NoMode
915926
) {
916927
// Never double-invoke effects for legacy roots.
917-
workInProgress.flags |= MountLayoutDev | Update;
918-
} else {
919-
workInProgress.flags |= Update;
928+
fiberFlags |= MountLayoutDev;
920929
}
930+
workInProgress.flags |= fiberFlags;
921931
}
922932
}
923933

@@ -987,16 +997,19 @@ function resumeMountClassInstance(
987997
// If an update was already in progress, we should schedule an Update
988998
// effect even though we're bailing out, so that cWU/cDU are called.
989999
if (typeof instance.componentDidMount === 'function') {
1000+
let fiberFlags: Flags = Update;
1001+
if (enableSuspenseLayoutEffectSemantics) {
1002+
fiberFlags |= LayoutStatic;
1003+
}
9901004
if (
9911005
__DEV__ &&
9921006
enableStrictEffects &&
9931007
(workInProgress.mode & StrictEffectsMode) !== NoMode
9941008
) {
9951009
// Never double-invoke effects for legacy roots.
996-
workInProgress.flags |= MountLayoutDev | Update;
997-
} else {
998-
workInProgress.flags |= Update;
1010+
fiberFlags |= MountLayoutDev;
9991011
}
1012+
workInProgress.flags |= fiberFlags;
10001013
}
10011014
return false;
10021015
}
@@ -1039,31 +1052,37 @@ function resumeMountClassInstance(
10391052
}
10401053
}
10411054
if (typeof instance.componentDidMount === 'function') {
1055+
let fiberFlags: Flags = Update;
1056+
if (enableSuspenseLayoutEffectSemantics) {
1057+
fiberFlags |= LayoutStatic;
1058+
}
10421059
if (
10431060
__DEV__ &&
10441061
enableStrictEffects &&
10451062
(workInProgress.mode & StrictEffectsMode) !== NoMode
10461063
) {
10471064
// Never double-invoke effects for legacy roots.
1048-
workInProgress.flags |= MountLayoutDev | Update;
1049-
} else {
1050-
workInProgress.flags |= Update;
1065+
fiberFlags |= MountLayoutDev;
10511066
}
1067+
workInProgress.flags |= fiberFlags;
10521068
}
10531069
} else {
10541070
// If an update was already in progress, we should schedule an Update
10551071
// effect even though we're bailing out, so that cWU/cDU are called.
10561072
if (typeof instance.componentDidMount === 'function') {
1073+
let fiberFlags: Flags = Update;
1074+
if (enableSuspenseLayoutEffectSemantics) {
1075+
fiberFlags |= LayoutStatic;
1076+
}
10571077
if (
10581078
__DEV__ &&
10591079
enableStrictEffects &&
10601080
(workInProgress.mode & StrictEffectsMode) !== NoMode
10611081
) {
10621082
// Never double-invoke effects for legacy roots.
1063-
workInProgress.flags |= MountLayoutDev | Update;
1064-
} else {
1065-
workInProgress.flags |= Update;
1083+
fiberFlags |= MountLayoutDev;
10661084
}
1085+
workInProgress.flags |= fiberFlags;
10671086
}
10681087

10691088
// If shouldComponentUpdate returned false, we should still update the

packages/react-reconciler/src/ReactFiberClassComponent.old.js

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,15 @@
1010
import type {Fiber} from './ReactInternalTypes';
1111
import type {Lanes} from './ReactFiberLane.old';
1212
import type {UpdateQueue} from './ReactUpdateQueue.old';
13+
import type {Flags} from './ReactFiberFlags';
1314

1415
import * as React from 'react';
15-
import {MountLayoutDev, Update, Snapshot} from './ReactFiberFlags';
16+
import {
17+
LayoutStatic,
18+
MountLayoutDev,
19+
Update,
20+
Snapshot,
21+
} from './ReactFiberFlags';
1622
import {
1723
debugRenderPhaseSideEffectsForStrictMode,
1824
disableLegacyContext,
@@ -21,6 +27,7 @@ import {
2127
warnAboutDeprecatedLifecycles,
2228
enableStrictEffects,
2329
enableLazyContextPropagation,
30+
enableSuspenseLayoutEffectSemantics,
2431
} from 'shared/ReactFeatureFlags';
2532
import ReactStrictModeWarnings from './ReactStrictModeWarnings.old';
2633
import {isMounted} from './ReactFiberTreeReflection';
@@ -908,16 +915,19 @@ function mountClassInstance(
908915
}
909916

910917
if (typeof instance.componentDidMount === 'function') {
918+
let fiberFlags: Flags = Update;
919+
if (enableSuspenseLayoutEffectSemantics) {
920+
fiberFlags |= LayoutStatic;
921+
}
911922
if (
912923
__DEV__ &&
913924
enableStrictEffects &&
914925
(workInProgress.mode & StrictEffectsMode) !== NoMode
915926
) {
916927
// Never double-invoke effects for legacy roots.
917-
workInProgress.flags |= MountLayoutDev | Update;
918-
} else {
919-
workInProgress.flags |= Update;
928+
fiberFlags |= MountLayoutDev;
920929
}
930+
workInProgress.flags |= fiberFlags;
921931
}
922932
}
923933

@@ -987,16 +997,19 @@ function resumeMountClassInstance(
987997
// If an update was already in progress, we should schedule an Update
988998
// effect even though we're bailing out, so that cWU/cDU are called.
989999
if (typeof instance.componentDidMount === 'function') {
1000+
let fiberFlags: Flags = Update;
1001+
if (enableSuspenseLayoutEffectSemantics) {
1002+
fiberFlags |= LayoutStatic;
1003+
}
9901004
if (
9911005
__DEV__ &&
9921006
enableStrictEffects &&
9931007
(workInProgress.mode & StrictEffectsMode) !== NoMode
9941008
) {
9951009
// Never double-invoke effects for legacy roots.
996-
workInProgress.flags |= MountLayoutDev | Update;
997-
} else {
998-
workInProgress.flags |= Update;
1010+
fiberFlags |= MountLayoutDev;
9991011
}
1012+
workInProgress.flags |= fiberFlags;
10001013
}
10011014
return false;
10021015
}
@@ -1039,31 +1052,37 @@ function resumeMountClassInstance(
10391052
}
10401053
}
10411054
if (typeof instance.componentDidMount === 'function') {
1055+
let fiberFlags: Flags = Update;
1056+
if (enableSuspenseLayoutEffectSemantics) {
1057+
fiberFlags |= LayoutStatic;
1058+
}
10421059
if (
10431060
__DEV__ &&
10441061
enableStrictEffects &&
10451062
(workInProgress.mode & StrictEffectsMode) !== NoMode
10461063
) {
10471064
// Never double-invoke effects for legacy roots.
1048-
workInProgress.flags |= MountLayoutDev | Update;
1049-
} else {
1050-
workInProgress.flags |= Update;
1065+
fiberFlags |= MountLayoutDev;
10511066
}
1067+
workInProgress.flags |= fiberFlags;
10521068
}
10531069
} else {
10541070
// If an update was already in progress, we should schedule an Update
10551071
// effect even though we're bailing out, so that cWU/cDU are called.
10561072
if (typeof instance.componentDidMount === 'function') {
1073+
let fiberFlags: Flags = Update;
1074+
if (enableSuspenseLayoutEffectSemantics) {
1075+
fiberFlags |= LayoutStatic;
1076+
}
10571077
if (
10581078
__DEV__ &&
10591079
enableStrictEffects &&
10601080
(workInProgress.mode & StrictEffectsMode) !== NoMode
10611081
) {
10621082
// Never double-invoke effects for legacy roots.
1063-
workInProgress.flags |= MountLayoutDev | Update;
1064-
} else {
1065-
workInProgress.flags |= Update;
1083+
fiberFlags |= MountLayoutDev;
10661084
}
1085+
workInProgress.flags |= fiberFlags;
10671086
}
10681087

10691088
// If shouldComponentUpdate returned false, we should still update the

0 commit comments

Comments
 (0)