@@ -980,12 +980,6 @@ function reappearLayoutEffectsOnFiber(node: Fiber) {
980
980
}
981
981
982
982
function hideOrUnhideAllChildren ( finishedWork , isHidden ) {
983
- // Suspense layout effects semantics don't change for legacy roots.
984
- const isModernRoot = ( finishedWork . mode & ConcurrentMode ) !== NoMode ;
985
-
986
- const current = finishedWork . alternate ;
987
- const wasHidden = current !== null && current . memoizedState !== null ;
988
-
989
983
// Only hide or unhide the top-most host nodes.
990
984
let hostSubtreeRoot = null ;
991
985
@@ -1005,22 +999,6 @@ function hideOrUnhideAllChildren(finishedWork, isHidden) {
1005
999
unhideInstance ( node . stateNode , node . memoizedProps ) ;
1006
1000
}
1007
1001
}
1008
-
1009
- if ( enableSuspenseLayoutEffectSemantics && isModernRoot ) {
1010
- // This method is called during mutation; it should detach refs within a hidden subtree.
1011
- // Attaching refs should be done elsewhere though (during layout).
1012
- // TODO (Offscreen) Also check: flags & RefStatic
1013
- if ( isHidden ) {
1014
- safelyDetachRef ( node , finishedWork ) ;
1015
- }
1016
-
1017
- // TODO (Offscreen) Also check: subtreeFlags & (RefStatic | LayoutStatic)
1018
- if ( node . child !== null ) {
1019
- node . child . return = node ;
1020
- node = node . child ;
1021
- continue ;
1022
- }
1023
- }
1024
1002
} else if ( node . tag === HostText ) {
1025
1003
if ( hostSubtreeRoot === null ) {
1026
1004
const instance = node . stateNode ;
@@ -1038,52 +1016,6 @@ function hideOrUnhideAllChildren(finishedWork, isHidden) {
1038
1016
) {
1039
1017
// Found a nested Offscreen component that is hidden.
1040
1018
// Don't search any deeper. This tree should remain hidden.
1041
- } else if ( enableSuspenseLayoutEffectSemantics && isModernRoot ) {
1042
- // When a mounted Suspense subtree gets hidden again, destroy any nested layout effects.
1043
- // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
1044
- switch ( node . tag ) {
1045
- case FunctionComponent :
1046
- case ForwardRef :
1047
- case MemoComponent :
1048
- case SimpleMemoComponent : {
1049
- // Note that refs are attached by the useImperativeHandle() hook, not by commitAttachRef()
1050
- if ( isHidden && ! wasHidden ) {
1051
- if (
1052
- enableProfilerTimer &&
1053
- enableProfilerCommitHooks &&
1054
- node . mode & ProfileMode
1055
- ) {
1056
- try {
1057
- startLayoutEffectTimer ( ) ;
1058
- commitHookEffectListUnmount ( HookLayout , node , finishedWork ) ;
1059
- } finally {
1060
- recordLayoutEffectDuration ( node ) ;
1061
- }
1062
- } else {
1063
- commitHookEffectListUnmount ( HookLayout , node , finishedWork ) ;
1064
- }
1065
- }
1066
- break ;
1067
- }
1068
- case ClassComponent : {
1069
- if ( isHidden && ! wasHidden ) {
1070
- // TODO (Offscreen) Check: flags & RefStatic
1071
- safelyDetachRef ( node , finishedWork ) ;
1072
-
1073
- const instance = node . stateNode ;
1074
- if ( typeof instance . componentWillUnmount === 'function' ) {
1075
- safelyCallComponentWillUnmount ( node , finishedWork , instance ) ;
1076
- }
1077
- }
1078
- break ;
1079
- }
1080
- }
1081
-
1082
- if ( node . child !== null ) {
1083
- node . child . return = node ;
1084
- node = node . child ;
1085
- continue ;
1086
- }
1087
1019
} else if ( node . child !== null ) {
1088
1020
node . child . return = node ;
1089
1021
node = node . child ;
@@ -1801,6 +1733,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
1801
1733
// This prevents sibling component effects from interfering with each other,
1802
1734
// e.g. a destroy function in one component should never override a ref set
1803
1735
// by a create function in another component during the same commit.
1736
+ // TODO: Check if we're inside an Offscreen subtree that disappeared
1737
+ // during this commit. If so, we would have already unmounted its
1738
+ // layout hooks. (However, since we null out the `destroy` function
1739
+ // right before calling it, the behavior is already correct, so this
1740
+ // would mostly be for modeling purposes.)
1804
1741
if (
1805
1742
enableProfilerTimer &&
1806
1743
enableProfilerCommitHooks &&
@@ -2183,28 +2120,73 @@ function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
2183
2120
switch ( finishedWork . tag ) {
2184
2121
case SuspenseComponent : {
2185
2122
const newState : OffscreenState | null = finishedWork . memoizedState ;
2186
- if ( newState !== null ) {
2187
- markCommitTimeOfFallback ( ) ;
2188
- // Hide the Offscreen component that contains the primary children.
2189
- // TODO: Ideally, this effect would have been scheduled on the
2190
- // Offscreen fiber itself. That's how unhiding works: the Offscreen
2191
- // component schedules an effect on itself. However, in this case, the
2192
- // component didn't complete, so the fiber was never added to the
2193
- // effect list in the normal path. We could have appended it to the
2194
- // effect list in the Suspense component's second pass, but doing it
2195
- // this way is less complicated. This would be simpler if we got rid
2196
- // of the effect list and traversed the tree, like we're planning to
2197
- // do.
2198
- const primaryChildParent : Fiber = ( finishedWork . child : any ) ;
2199
- hideOrUnhideAllChildren ( primaryChildParent , true ) ;
2123
+ const isHidden = newState !== null ;
2124
+ const current = finishedWork . alternate ;
2125
+ const wasHidden = current !== null && current . memoizedState !== null ;
2126
+ const offscreenBoundary : Fiber = ( finishedWork . child : any ) ;
2127
+
2128
+ if ( isHidden ) {
2129
+ if ( ! wasHidden ) {
2130
+ markCommitTimeOfFallback ( ) ;
2131
+ if ( supportsMutation ) {
2132
+ hideOrUnhideAllChildren ( offscreenBoundary , true ) ;
2133
+ }
2134
+ if (
2135
+ enableSuspenseLayoutEffectSemantics &&
2136
+ ( offscreenBoundary . mode & ConcurrentMode ) !== NoMode
2137
+ ) {
2138
+ let offscreenChild = offscreenBoundary . child ;
2139
+ while ( offscreenChild !== null ) {
2140
+ nextEffect = offscreenChild ;
2141
+ disappearLayoutEffects_begin ( offscreenChild ) ;
2142
+ offscreenChild = offscreenChild . sibling ;
2143
+ }
2144
+ }
2145
+ }
2146
+ } else {
2147
+ if ( wasHidden ) {
2148
+ if ( supportsMutation ) {
2149
+ hideOrUnhideAllChildren ( offscreenBoundary , false ) ;
2150
+ }
2151
+ // TODO: Move re-appear call here for symmetry?
2152
+ }
2200
2153
}
2201
2154
break ;
2202
2155
}
2203
2156
case OffscreenComponent:
2204
2157
case LegacyHiddenComponent : {
2205
2158
const newState : OffscreenState | null = finishedWork . memoizedState ;
2206
2159
const isHidden = newState !== null ;
2207
- hideOrUnhideAllChildren ( finishedWork , isHidden ) ;
2160
+ const current = finishedWork . alternate ;
2161
+ const wasHidden = current !== null && current . memoizedState !== null ;
2162
+ const offscreenBoundary : Fiber = finishedWork ;
2163
+
2164
+ if ( supportsMutation ) {
2165
+ // TODO: This needs to run whenever there's an insertion or update
2166
+ // inside a hidden Offscreen tree.
2167
+ hideOrUnhideAllChildren ( offscreenBoundary , isHidden ) ;
2168
+ }
2169
+
2170
+ if ( isHidden ) {
2171
+ if ( ! wasHidden ) {
2172
+ if (
2173
+ enableSuspenseLayoutEffectSemantics &&
2174
+ ( offscreenBoundary . mode & ConcurrentMode ) !== NoMode
2175
+ ) {
2176
+ nextEffect = offscreenBoundary ;
2177
+ let offscreenChild = offscreenBoundary . child ;
2178
+ while ( offscreenChild !== null ) {
2179
+ nextEffect = offscreenChild ;
2180
+ disappearLayoutEffects_begin ( offscreenChild ) ;
2181
+ offscreenChild = offscreenChild . sibling ;
2182
+ }
2183
+ }
2184
+ }
2185
+ } else {
2186
+ if ( wasHidden ) {
2187
+ // TODO: Move re-appear call here for symmetry?
2188
+ }
2189
+ }
2208
2190
break ;
2209
2191
}
2210
2192
}
@@ -2381,6 +2363,90 @@ function commitLayoutMountEffects_complete(
2381
2363
}
2382
2364
}
2383
2365
2366
+ function disappearLayoutEffects_begin ( subtreeRoot : Fiber ) {
2367
+ while ( nextEffect !== null ) {
2368
+ const fiber = nextEffect ;
2369
+ const firstChild = fiber . child ;
2370
+
2371
+ // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
2372
+ switch ( fiber . tag ) {
2373
+ case FunctionComponent :
2374
+ case ForwardRef :
2375
+ case MemoComponent :
2376
+ case SimpleMemoComponent : {
2377
+ if (
2378
+ enableProfilerTimer &&
2379
+ enableProfilerCommitHooks &&
2380
+ fiber . mode & ProfileMode
2381
+ ) {
2382
+ try {
2383
+ startLayoutEffectTimer ( ) ;
2384
+ commitHookEffectListUnmount ( HookLayout , fiber , fiber . return ) ;
2385
+ } finally {
2386
+ recordLayoutEffectDuration ( fiber ) ;
2387
+ }
2388
+ } else {
2389
+ commitHookEffectListUnmount ( HookLayout , fiber , fiber . return ) ;
2390
+ }
2391
+ break ;
2392
+ }
2393
+ case ClassComponent : {
2394
+ // TODO (Offscreen) Check: flags & RefStatic
2395
+ safelyDetachRef ( fiber , fiber . return ) ;
2396
+
2397
+ const instance = fiber . stateNode ;
2398
+ if ( typeof instance . componentWillUnmount === 'function' ) {
2399
+ safelyCallComponentWillUnmount ( fiber , fiber . return , instance ) ;
2400
+ }
2401
+ break ;
2402
+ }
2403
+ case HostComponent : {
2404
+ safelyDetachRef ( fiber , fiber . return ) ;
2405
+ break ;
2406
+ }
2407
+ case OffscreenComponent : {
2408
+ // Check if this is a
2409
+ const isHidden = fiber . memoizedState !== null ;
2410
+ if ( isHidden ) {
2411
+ // Nested Offscreen tree is already hidden. Don't disappear
2412
+ // its effects.
2413
+ disappearLayoutEffects_complete ( subtreeRoot ) ;
2414
+ continue ;
2415
+ }
2416
+ break ;
2417
+ }
2418
+ }
2419
+
2420
+ // TODO (Offscreen) Check: subtreeFlags & LayoutStatic
2421
+ if ( firstChild !== null ) {
2422
+ firstChild . return = fiber ;
2423
+ nextEffect = firstChild ;
2424
+ } else {
2425
+ disappearLayoutEffects_complete ( subtreeRoot ) ;
2426
+ }
2427
+ }
2428
+ }
2429
+
2430
+ function disappearLayoutEffects_complete ( subtreeRoot : Fiber ) {
2431
+ while ( nextEffect !== null ) {
2432
+ const fiber = nextEffect ;
2433
+
2434
+ if ( fiber === subtreeRoot ) {
2435
+ nextEffect = null ;
2436
+ return ;
2437
+ }
2438
+
2439
+ const sibling = fiber . sibling ;
2440
+ if ( sibling !== null ) {
2441
+ sibling . return = fiber . return ;
2442
+ nextEffect = sibling ;
2443
+ return ;
2444
+ }
2445
+
2446
+ nextEffect = fiber . return ;
2447
+ }
2448
+ }
2449
+
2384
2450
function reappearLayoutEffects_begin ( subtreeRoot : Fiber ) {
2385
2451
while ( nextEffect !== null ) {
2386
2452
const fiber = nextEffect ;
@@ -2397,7 +2463,9 @@ function reappearLayoutEffects_begin(subtreeRoot: Fiber) {
2397
2463
2398
2464
// TODO (Offscreen) Check: subtreeFlags & LayoutStatic
2399
2465
if ( firstChild !== null ) {
2400
- ensureCorrectReturnPointer ( firstChild , fiber ) ;
2466
+ // This node may have been reused from a previous render, so we can't
2467
+ // assume its return pointer is correct.
2468
+ firstChild . return = fiber ;
2401
2469
nextEffect = firstChild ;
2402
2470
} else {
2403
2471
reappearLayoutEffects_complete ( subtreeRoot ) ;
@@ -2426,7 +2494,9 @@ function reappearLayoutEffects_complete(subtreeRoot: Fiber) {
2426
2494
2427
2495
const sibling = fiber . sibling ;
2428
2496
if ( sibling !== null ) {
2429
- ensureCorrectReturnPointer ( sibling , fiber . return ) ;
2497
+ // This node may have been reused from a previous render, so we can't
2498
+ // assume its return pointer is correct.
2499
+ sibling . return = fiber . return ;
2430
2500
nextEffect = sibling ;
2431
2501
return ;
2432
2502
}
0 commit comments