@@ -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,8 +2120,12 @@ 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 ( ) ;
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 ) {
2188
2129
// Hide the Offscreen component that contains the primary children.
2189
2130
// TODO: Ideally, this effect would have been scheduled on the
2190
2131
// Offscreen fiber itself. That's how unhiding works: the Offscreen
@@ -2195,16 +2136,67 @@ function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
2195
2136
// this way is less complicated. This would be simpler if we got rid
2196
2137
// of the effect list and traversed the tree, like we're planning to
2197
2138
// do.
2198
- const primaryChildParent : Fiber = ( finishedWork . child : any ) ;
2199
- hideOrUnhideAllChildren ( primaryChildParent , true ) ;
2139
+ if ( ! wasHidden ) {
2140
+ markCommitTimeOfFallback ( ) ;
2141
+ if ( supportsMutation ) {
2142
+ hideOrUnhideAllChildren ( offscreenBoundary , true ) ;
2143
+ }
2144
+ if (
2145
+ enableSuspenseLayoutEffectSemantics &&
2146
+ ( offscreenBoundary . mode & ConcurrentMode ) !== NoMode
2147
+ ) {
2148
+ let offscreenChild = offscreenBoundary . child ;
2149
+ while ( offscreenChild !== null ) {
2150
+ nextEffect = offscreenChild ;
2151
+ disappearLayoutEffects_begin ( offscreenChild ) ;
2152
+ offscreenChild = offscreenChild . sibling ;
2153
+ }
2154
+ }
2155
+ }
2156
+ } else {
2157
+ if ( wasHidden ) {
2158
+ if ( supportsMutation ) {
2159
+ hideOrUnhideAllChildren ( offscreenBoundary , false ) ;
2160
+ }
2161
+ // TODO: Move re-appear call here for symmetry?
2162
+ }
2200
2163
}
2201
2164
break ;
2202
2165
}
2203
2166
case OffscreenComponent:
2204
2167
case LegacyHiddenComponent : {
2205
2168
const newState : OffscreenState | null = finishedWork . memoizedState ;
2206
2169
const isHidden = newState !== null ;
2207
- hideOrUnhideAllChildren ( finishedWork , isHidden ) ;
2170
+ const current = finishedWork . alternate ;
2171
+ const wasHidden = current !== null && current . memoizedState !== null ;
2172
+ const offscreenBoundary : Fiber = finishedWork ;
2173
+
2174
+ if ( supportsMutation ) {
2175
+ // TODO: This needs to run whenever there's an insertion or update
2176
+ // inside a hidden Offscreen tree.
2177
+ hideOrUnhideAllChildren ( offscreenBoundary , isHidden ) ;
2178
+ }
2179
+
2180
+ if ( isHidden ) {
2181
+ if ( ! wasHidden ) {
2182
+ if (
2183
+ enableSuspenseLayoutEffectSemantics &&
2184
+ ( offscreenBoundary . mode & ConcurrentMode ) !== NoMode
2185
+ ) {
2186
+ nextEffect = offscreenBoundary ;
2187
+ let offscreenChild = offscreenBoundary . child ;
2188
+ while ( offscreenChild !== null ) {
2189
+ nextEffect = offscreenChild ;
2190
+ disappearLayoutEffects_begin ( offscreenChild ) ;
2191
+ offscreenChild = offscreenChild . sibling ;
2192
+ }
2193
+ }
2194
+ }
2195
+ } else {
2196
+ if ( wasHidden ) {
2197
+ // TODO: Move re-appear call here for symmetry?
2198
+ }
2199
+ }
2208
2200
break ;
2209
2201
}
2210
2202
}
@@ -2381,6 +2373,90 @@ function commitLayoutMountEffects_complete(
2381
2373
}
2382
2374
}
2383
2375
2376
+ function disappearLayoutEffects_begin ( subtreeRoot : Fiber ) {
2377
+ while ( nextEffect !== null ) {
2378
+ const fiber = nextEffect ;
2379
+ const firstChild = fiber . child ;
2380
+
2381
+ // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic)
2382
+ switch ( fiber . tag ) {
2383
+ case FunctionComponent :
2384
+ case ForwardRef :
2385
+ case MemoComponent :
2386
+ case SimpleMemoComponent : {
2387
+ if (
2388
+ enableProfilerTimer &&
2389
+ enableProfilerCommitHooks &&
2390
+ fiber . mode & ProfileMode
2391
+ ) {
2392
+ try {
2393
+ startLayoutEffectTimer ( ) ;
2394
+ commitHookEffectListUnmount ( HookLayout , fiber , fiber . return ) ;
2395
+ } finally {
2396
+ recordLayoutEffectDuration ( fiber ) ;
2397
+ }
2398
+ } else {
2399
+ commitHookEffectListUnmount ( HookLayout , fiber , fiber . return ) ;
2400
+ }
2401
+ break ;
2402
+ }
2403
+ case ClassComponent : {
2404
+ // TODO (Offscreen) Check: flags & RefStatic
2405
+ safelyDetachRef ( fiber , fiber . return ) ;
2406
+
2407
+ const instance = fiber . stateNode ;
2408
+ if ( typeof instance . componentWillUnmount === 'function' ) {
2409
+ safelyCallComponentWillUnmount ( fiber , fiber . return , instance ) ;
2410
+ }
2411
+ break ;
2412
+ }
2413
+ case HostComponent : {
2414
+ safelyDetachRef ( fiber , fiber . return ) ;
2415
+ break ;
2416
+ }
2417
+ case OffscreenComponent : {
2418
+ // Check if this is a
2419
+ const isHidden = fiber . memoizedState !== null ;
2420
+ if ( isHidden ) {
2421
+ // Nested Offscreen tree is already hidden. Don't disappear
2422
+ // its effects.
2423
+ disappearLayoutEffects_complete ( subtreeRoot ) ;
2424
+ continue ;
2425
+ }
2426
+ break ;
2427
+ }
2428
+ }
2429
+
2430
+ // TODO (Offscreen) Check: subtreeFlags & LayoutStatic
2431
+ if ( firstChild !== null ) {
2432
+ firstChild . return = fiber ;
2433
+ nextEffect = firstChild ;
2434
+ } else {
2435
+ disappearLayoutEffects_complete ( subtreeRoot ) ;
2436
+ }
2437
+ }
2438
+ }
2439
+
2440
+ function disappearLayoutEffects_complete ( subtreeRoot : Fiber ) {
2441
+ while ( nextEffect !== null ) {
2442
+ const fiber = nextEffect ;
2443
+
2444
+ if ( fiber === subtreeRoot ) {
2445
+ nextEffect = null ;
2446
+ return ;
2447
+ }
2448
+
2449
+ const sibling = fiber . sibling ;
2450
+ if ( sibling !== null ) {
2451
+ sibling . return = fiber . return ;
2452
+ nextEffect = sibling ;
2453
+ return ;
2454
+ }
2455
+
2456
+ nextEffect = fiber . return ;
2457
+ }
2458
+ }
2459
+
2384
2460
function reappearLayoutEffects_begin ( subtreeRoot : Fiber ) {
2385
2461
while ( nextEffect !== null ) {
2386
2462
const fiber = nextEffect ;
@@ -2397,7 +2473,9 @@ function reappearLayoutEffects_begin(subtreeRoot: Fiber) {
2397
2473
2398
2474
// TODO (Offscreen) Check: subtreeFlags & LayoutStatic
2399
2475
if ( firstChild !== null ) {
2400
- ensureCorrectReturnPointer ( firstChild , fiber ) ;
2476
+ // This node may have been reused from a previous render, so we can't
2477
+ // assume its return pointer is correct.
2478
+ firstChild . return = fiber ;
2401
2479
nextEffect = firstChild ;
2402
2480
} else {
2403
2481
reappearLayoutEffects_complete ( subtreeRoot ) ;
@@ -2426,7 +2504,9 @@ function reappearLayoutEffects_complete(subtreeRoot: Fiber) {
2426
2504
2427
2505
const sibling = fiber . sibling ;
2428
2506
if ( sibling !== null ) {
2429
- ensureCorrectReturnPointer ( sibling , fiber . return ) ;
2507
+ // This node may have been reused from a previous render, so we can't
2508
+ // assume its return pointer is correct.
2509
+ sibling . return = fiber . return ;
2430
2510
nextEffect = sibling ;
2431
2511
return ;
2432
2512
}
0 commit comments