@@ -980,12 +980,6 @@ function reappearLayoutEffectsOnFiber(node: Fiber) {
980980}
981981
982982function 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-
989983 // Only hide or unhide the top-most host nodes.
990984 let hostSubtreeRoot = null ;
991985
@@ -1005,22 +999,6 @@ function hideOrUnhideAllChildren(finishedWork, isHidden) {
1005999 unhideInstance ( node . stateNode , node . memoizedProps ) ;
10061000 }
10071001 }
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- }
10241002 } else if ( node . tag === HostText ) {
10251003 if ( hostSubtreeRoot === null ) {
10261004 const instance = node . stateNode ;
@@ -1038,52 +1016,6 @@ function hideOrUnhideAllChildren(finishedWork, isHidden) {
10381016 ) {
10391017 // Found a nested Offscreen component that is hidden.
10401018 // 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- }
10871019 } else if ( node . child !== null ) {
10881020 node . child . return = node ;
10891021 node = node . child ;
@@ -1801,6 +1733,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void {
18011733 // This prevents sibling component effects from interfering with each other,
18021734 // e.g. a destroy function in one component should never override a ref set
18031735 // 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.)
18041741 if (
18051742 enableProfilerTimer &&
18061743 enableProfilerCommitHooks &&
@@ -2183,28 +2120,73 @@ function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) {
21832120 switch ( finishedWork . tag ) {
21842121 case SuspenseComponent : {
21852122 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+ }
22002153 }
22012154 break ;
22022155 }
22032156 case OffscreenComponent:
22042157 case LegacyHiddenComponent : {
22052158 const newState : OffscreenState | null = finishedWork . memoizedState ;
22062159 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+ }
22082190 break ;
22092191 }
22102192 }
@@ -2381,6 +2363,90 @@ function commitLayoutMountEffects_complete(
23812363 }
23822364}
23832365
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+
23842450function reappearLayoutEffects_begin ( subtreeRoot : Fiber ) {
23852451 while ( nextEffect !== null ) {
23862452 const fiber = nextEffect ;
@@ -2397,7 +2463,9 @@ function reappearLayoutEffects_begin(subtreeRoot: Fiber) {
23972463
23982464 // TODO (Offscreen) Check: subtreeFlags & LayoutStatic
23992465 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 ;
24012469 nextEffect = firstChild ;
24022470 } else {
24032471 reappearLayoutEffects_complete ( subtreeRoot ) ;
@@ -2426,7 +2494,9 @@ function reappearLayoutEffects_complete(subtreeRoot: Fiber) {
24262494
24272495 const sibling = fiber . sibling ;
24282496 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 ;
24302500 nextEffect = sibling ;
24312501 return ;
24322502 }
0 commit comments