From 1c469c719273cfa1841a1d46a6d425990554b017 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Fri, 16 Jul 2021 18:00:11 -0400 Subject: [PATCH] Refactor "disappear" logic into its own traversal Similar to #21898, but for "disappear" logic. Previously this lived inside `hideOrUnhideAllChildren`, the function that mutates the nearest DOM nodes to override their `display` style. This makes the feature work in persistent mode (Fabric); it didn't before because `hideOrUnhideAllChildren` only runs in mutation mode. --- .../src/ReactFiberCommitWork.new.js | 240 +++++++++++------- .../src/ReactFiberCommitWork.old.js | 240 +++++++++++------- .../src/__tests__/ReactOffscreen-test.js | 2 - .../ReactSuspenseEffectsSemantics-test.js | 20 -- 4 files changed, 310 insertions(+), 192 deletions(-) diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js index e211ff332b79c..99161bd97f487 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js @@ -980,12 +980,6 @@ function reappearLayoutEffectsOnFiber(node: Fiber) { } function hideOrUnhideAllChildren(finishedWork, isHidden) { - // Suspense layout effects semantics don't change for legacy roots. - const isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode; - - const current = finishedWork.alternate; - const wasHidden = current !== null && current.memoizedState !== null; - // Only hide or unhide the top-most host nodes. let hostSubtreeRoot = null; @@ -1005,22 +999,6 @@ function hideOrUnhideAllChildren(finishedWork, isHidden) { unhideInstance(node.stateNode, node.memoizedProps); } } - - if (enableSuspenseLayoutEffectSemantics && isModernRoot) { - // This method is called during mutation; it should detach refs within a hidden subtree. - // Attaching refs should be done elsewhere though (during layout). - // TODO (Offscreen) Also check: flags & RefStatic - if (isHidden) { - safelyDetachRef(node, finishedWork); - } - - // TODO (Offscreen) Also check: subtreeFlags & (RefStatic | LayoutStatic) - if (node.child !== null) { - node.child.return = node; - node = node.child; - continue; - } - } } else if (node.tag === HostText) { if (hostSubtreeRoot === null) { const instance = node.stateNode; @@ -1038,52 +1016,6 @@ function hideOrUnhideAllChildren(finishedWork, isHidden) { ) { // Found a nested Offscreen component that is hidden. // Don't search any deeper. This tree should remain hidden. - } else if (enableSuspenseLayoutEffectSemantics && isModernRoot) { - // When a mounted Suspense subtree gets hidden again, destroy any nested layout effects. - // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic) - switch (node.tag) { - case FunctionComponent: - case ForwardRef: - case MemoComponent: - case SimpleMemoComponent: { - // Note that refs are attached by the useImperativeHandle() hook, not by commitAttachRef() - if (isHidden && !wasHidden) { - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - node.mode & ProfileMode - ) { - try { - startLayoutEffectTimer(); - commitHookEffectListUnmount(HookLayout, node, finishedWork); - } finally { - recordLayoutEffectDuration(node); - } - } else { - commitHookEffectListUnmount(HookLayout, node, finishedWork); - } - } - break; - } - case ClassComponent: { - if (isHidden && !wasHidden) { - // TODO (Offscreen) Check: flags & RefStatic - safelyDetachRef(node, finishedWork); - - const instance = node.stateNode; - if (typeof instance.componentWillUnmount === 'function') { - safelyCallComponentWillUnmount(node, finishedWork, instance); - } - } - break; - } - } - - if (node.child !== null) { - node.child.return = node; - node = node.child; - continue; - } } else if (node.child !== null) { node.child.return = node; node = node.child; @@ -1801,6 +1733,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void { // This prevents sibling component effects from interfering with each other, // e.g. a destroy function in one component should never override a ref set // by a create function in another component during the same commit. + // TODO: Check if we're inside an Offscreen subtree that disappeared + // during this commit. If so, we would have already unmounted its + // layout hooks. (However, since we null out the `destroy` function + // right before calling it, the behavior is already correct, so this + // would mostly be for modeling purposes.) if ( enableProfilerTimer && enableProfilerCommitHooks && @@ -2183,20 +2120,36 @@ function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) { switch (finishedWork.tag) { case SuspenseComponent: { const newState: OffscreenState | null = finishedWork.memoizedState; - if (newState !== null) { - markCommitTimeOfFallback(); - // Hide the Offscreen component that contains the primary children. - // TODO: Ideally, this effect would have been scheduled on the - // Offscreen fiber itself. That's how unhiding works: the Offscreen - // component schedules an effect on itself. However, in this case, the - // component didn't complete, so the fiber was never added to the - // effect list in the normal path. We could have appended it to the - // effect list in the Suspense component's second pass, but doing it - // this way is less complicated. This would be simpler if we got rid - // of the effect list and traversed the tree, like we're planning to - // do. - const primaryChildParent: Fiber = (finishedWork.child: any); - hideOrUnhideAllChildren(primaryChildParent, true); + const isHidden = newState !== null; + const current = finishedWork.alternate; + const wasHidden = current !== null && current.memoizedState !== null; + const offscreenBoundary: Fiber = (finishedWork.child: any); + + if (isHidden) { + if (!wasHidden) { + markCommitTimeOfFallback(); + if (supportsMutation) { + hideOrUnhideAllChildren(offscreenBoundary, true); + } + if ( + enableSuspenseLayoutEffectSemantics && + (offscreenBoundary.mode & ConcurrentMode) !== NoMode + ) { + let offscreenChild = offscreenBoundary.child; + while (offscreenChild !== null) { + nextEffect = offscreenChild; + disappearLayoutEffects_begin(offscreenChild); + offscreenChild = offscreenChild.sibling; + } + } + } + } else { + if (wasHidden) { + if (supportsMutation) { + hideOrUnhideAllChildren(offscreenBoundary, false); + } + // TODO: Move re-appear call here for symmetry? + } } break; } @@ -2204,7 +2157,36 @@ function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) { case LegacyHiddenComponent: { const newState: OffscreenState | null = finishedWork.memoizedState; const isHidden = newState !== null; - hideOrUnhideAllChildren(finishedWork, isHidden); + const current = finishedWork.alternate; + const wasHidden = current !== null && current.memoizedState !== null; + const offscreenBoundary: Fiber = finishedWork; + + if (supportsMutation) { + // TODO: This needs to run whenever there's an insertion or update + // inside a hidden Offscreen tree. + hideOrUnhideAllChildren(offscreenBoundary, isHidden); + } + + if (isHidden) { + if (!wasHidden) { + if ( + enableSuspenseLayoutEffectSemantics && + (offscreenBoundary.mode & ConcurrentMode) !== NoMode + ) { + nextEffect = offscreenBoundary; + let offscreenChild = offscreenBoundary.child; + while (offscreenChild !== null) { + nextEffect = offscreenChild; + disappearLayoutEffects_begin(offscreenChild); + offscreenChild = offscreenChild.sibling; + } + } + } + } else { + if (wasHidden) { + // TODO: Move re-appear call here for symmetry? + } + } break; } } @@ -2381,6 +2363,90 @@ function commitLayoutMountEffects_complete( } } +function disappearLayoutEffects_begin(subtreeRoot: Fiber) { + while (nextEffect !== null) { + const fiber = nextEffect; + const firstChild = fiber.child; + + // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic) + switch (fiber.tag) { + case FunctionComponent: + case ForwardRef: + case MemoComponent: + case SimpleMemoComponent: { + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + fiber.mode & ProfileMode + ) { + try { + startLayoutEffectTimer(); + commitHookEffectListUnmount(HookLayout, fiber, fiber.return); + } finally { + recordLayoutEffectDuration(fiber); + } + } else { + commitHookEffectListUnmount(HookLayout, fiber, fiber.return); + } + break; + } + case ClassComponent: { + // TODO (Offscreen) Check: flags & RefStatic + safelyDetachRef(fiber, fiber.return); + + const instance = fiber.stateNode; + if (typeof instance.componentWillUnmount === 'function') { + safelyCallComponentWillUnmount(fiber, fiber.return, instance); + } + break; + } + case HostComponent: { + safelyDetachRef(fiber, fiber.return); + break; + } + case OffscreenComponent: { + // Check if this is a + const isHidden = fiber.memoizedState !== null; + if (isHidden) { + // Nested Offscreen tree is already hidden. Don't disappear + // its effects. + disappearLayoutEffects_complete(subtreeRoot); + continue; + } + break; + } + } + + // TODO (Offscreen) Check: subtreeFlags & LayoutStatic + if (firstChild !== null) { + firstChild.return = fiber; + nextEffect = firstChild; + } else { + disappearLayoutEffects_complete(subtreeRoot); + } + } +} + +function disappearLayoutEffects_complete(subtreeRoot: Fiber) { + while (nextEffect !== null) { + const fiber = nextEffect; + + if (fiber === subtreeRoot) { + nextEffect = null; + return; + } + + const sibling = fiber.sibling; + if (sibling !== null) { + sibling.return = fiber.return; + nextEffect = sibling; + return; + } + + nextEffect = fiber.return; + } +} + function reappearLayoutEffects_begin(subtreeRoot: Fiber) { while (nextEffect !== null) { const fiber = nextEffect; @@ -2397,7 +2463,9 @@ function reappearLayoutEffects_begin(subtreeRoot: Fiber) { // TODO (Offscreen) Check: subtreeFlags & LayoutStatic if (firstChild !== null) { - ensureCorrectReturnPointer(firstChild, fiber); + // This node may have been reused from a previous render, so we can't + // assume its return pointer is correct. + firstChild.return = fiber; nextEffect = firstChild; } else { reappearLayoutEffects_complete(subtreeRoot); @@ -2426,7 +2494,9 @@ function reappearLayoutEffects_complete(subtreeRoot: Fiber) { const sibling = fiber.sibling; if (sibling !== null) { - ensureCorrectReturnPointer(sibling, fiber.return); + // This node may have been reused from a previous render, so we can't + // assume its return pointer is correct. + sibling.return = fiber.return; nextEffect = sibling; return; } diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js index b9706d730ba7d..8fcdb5920756e 100644 --- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js +++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js @@ -980,12 +980,6 @@ function reappearLayoutEffectsOnFiber(node: Fiber) { } function hideOrUnhideAllChildren(finishedWork, isHidden) { - // Suspense layout effects semantics don't change for legacy roots. - const isModernRoot = (finishedWork.mode & ConcurrentMode) !== NoMode; - - const current = finishedWork.alternate; - const wasHidden = current !== null && current.memoizedState !== null; - // Only hide or unhide the top-most host nodes. let hostSubtreeRoot = null; @@ -1005,22 +999,6 @@ function hideOrUnhideAllChildren(finishedWork, isHidden) { unhideInstance(node.stateNode, node.memoizedProps); } } - - if (enableSuspenseLayoutEffectSemantics && isModernRoot) { - // This method is called during mutation; it should detach refs within a hidden subtree. - // Attaching refs should be done elsewhere though (during layout). - // TODO (Offscreen) Also check: flags & RefStatic - if (isHidden) { - safelyDetachRef(node, finishedWork); - } - - // TODO (Offscreen) Also check: subtreeFlags & (RefStatic | LayoutStatic) - if (node.child !== null) { - node.child.return = node; - node = node.child; - continue; - } - } } else if (node.tag === HostText) { if (hostSubtreeRoot === null) { const instance = node.stateNode; @@ -1038,52 +1016,6 @@ function hideOrUnhideAllChildren(finishedWork, isHidden) { ) { // Found a nested Offscreen component that is hidden. // Don't search any deeper. This tree should remain hidden. - } else if (enableSuspenseLayoutEffectSemantics && isModernRoot) { - // When a mounted Suspense subtree gets hidden again, destroy any nested layout effects. - // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic) - switch (node.tag) { - case FunctionComponent: - case ForwardRef: - case MemoComponent: - case SimpleMemoComponent: { - // Note that refs are attached by the useImperativeHandle() hook, not by commitAttachRef() - if (isHidden && !wasHidden) { - if ( - enableProfilerTimer && - enableProfilerCommitHooks && - node.mode & ProfileMode - ) { - try { - startLayoutEffectTimer(); - commitHookEffectListUnmount(HookLayout, node, finishedWork); - } finally { - recordLayoutEffectDuration(node); - } - } else { - commitHookEffectListUnmount(HookLayout, node, finishedWork); - } - } - break; - } - case ClassComponent: { - if (isHidden && !wasHidden) { - // TODO (Offscreen) Check: flags & RefStatic - safelyDetachRef(node, finishedWork); - - const instance = node.stateNode; - if (typeof instance.componentWillUnmount === 'function') { - safelyCallComponentWillUnmount(node, finishedWork, instance); - } - } - break; - } - } - - if (node.child !== null) { - node.child.return = node; - node = node.child; - continue; - } } else if (node.child !== null) { node.child.return = node; node = node.child; @@ -1801,6 +1733,11 @@ function commitWork(current: Fiber | null, finishedWork: Fiber): void { // This prevents sibling component effects from interfering with each other, // e.g. a destroy function in one component should never override a ref set // by a create function in another component during the same commit. + // TODO: Check if we're inside an Offscreen subtree that disappeared + // during this commit. If so, we would have already unmounted its + // layout hooks. (However, since we null out the `destroy` function + // right before calling it, the behavior is already correct, so this + // would mostly be for modeling purposes.) if ( enableProfilerTimer && enableProfilerCommitHooks && @@ -2183,20 +2120,36 @@ function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) { switch (finishedWork.tag) { case SuspenseComponent: { const newState: OffscreenState | null = finishedWork.memoizedState; - if (newState !== null) { - markCommitTimeOfFallback(); - // Hide the Offscreen component that contains the primary children. - // TODO: Ideally, this effect would have been scheduled on the - // Offscreen fiber itself. That's how unhiding works: the Offscreen - // component schedules an effect on itself. However, in this case, the - // component didn't complete, so the fiber was never added to the - // effect list in the normal path. We could have appended it to the - // effect list in the Suspense component's second pass, but doing it - // this way is less complicated. This would be simpler if we got rid - // of the effect list and traversed the tree, like we're planning to - // do. - const primaryChildParent: Fiber = (finishedWork.child: any); - hideOrUnhideAllChildren(primaryChildParent, true); + const isHidden = newState !== null; + const current = finishedWork.alternate; + const wasHidden = current !== null && current.memoizedState !== null; + const offscreenBoundary: Fiber = (finishedWork.child: any); + + if (isHidden) { + if (!wasHidden) { + markCommitTimeOfFallback(); + if (supportsMutation) { + hideOrUnhideAllChildren(offscreenBoundary, true); + } + if ( + enableSuspenseLayoutEffectSemantics && + (offscreenBoundary.mode & ConcurrentMode) !== NoMode + ) { + let offscreenChild = offscreenBoundary.child; + while (offscreenChild !== null) { + nextEffect = offscreenChild; + disappearLayoutEffects_begin(offscreenChild); + offscreenChild = offscreenChild.sibling; + } + } + } + } else { + if (wasHidden) { + if (supportsMutation) { + hideOrUnhideAllChildren(offscreenBoundary, false); + } + // TODO: Move re-appear call here for symmetry? + } } break; } @@ -2204,7 +2157,36 @@ function commitMutationEffectsOnFiber(finishedWork: Fiber, root: FiberRoot) { case LegacyHiddenComponent: { const newState: OffscreenState | null = finishedWork.memoizedState; const isHidden = newState !== null; - hideOrUnhideAllChildren(finishedWork, isHidden); + const current = finishedWork.alternate; + const wasHidden = current !== null && current.memoizedState !== null; + const offscreenBoundary: Fiber = finishedWork; + + if (supportsMutation) { + // TODO: This needs to run whenever there's an insertion or update + // inside a hidden Offscreen tree. + hideOrUnhideAllChildren(offscreenBoundary, isHidden); + } + + if (isHidden) { + if (!wasHidden) { + if ( + enableSuspenseLayoutEffectSemantics && + (offscreenBoundary.mode & ConcurrentMode) !== NoMode + ) { + nextEffect = offscreenBoundary; + let offscreenChild = offscreenBoundary.child; + while (offscreenChild !== null) { + nextEffect = offscreenChild; + disappearLayoutEffects_begin(offscreenChild); + offscreenChild = offscreenChild.sibling; + } + } + } + } else { + if (wasHidden) { + // TODO: Move re-appear call here for symmetry? + } + } break; } } @@ -2381,6 +2363,90 @@ function commitLayoutMountEffects_complete( } } +function disappearLayoutEffects_begin(subtreeRoot: Fiber) { + while (nextEffect !== null) { + const fiber = nextEffect; + const firstChild = fiber.child; + + // TODO (Offscreen) Check: flags & (RefStatic | LayoutStatic) + switch (fiber.tag) { + case FunctionComponent: + case ForwardRef: + case MemoComponent: + case SimpleMemoComponent: { + if ( + enableProfilerTimer && + enableProfilerCommitHooks && + fiber.mode & ProfileMode + ) { + try { + startLayoutEffectTimer(); + commitHookEffectListUnmount(HookLayout, fiber, fiber.return); + } finally { + recordLayoutEffectDuration(fiber); + } + } else { + commitHookEffectListUnmount(HookLayout, fiber, fiber.return); + } + break; + } + case ClassComponent: { + // TODO (Offscreen) Check: flags & RefStatic + safelyDetachRef(fiber, fiber.return); + + const instance = fiber.stateNode; + if (typeof instance.componentWillUnmount === 'function') { + safelyCallComponentWillUnmount(fiber, fiber.return, instance); + } + break; + } + case HostComponent: { + safelyDetachRef(fiber, fiber.return); + break; + } + case OffscreenComponent: { + // Check if this is a + const isHidden = fiber.memoizedState !== null; + if (isHidden) { + // Nested Offscreen tree is already hidden. Don't disappear + // its effects. + disappearLayoutEffects_complete(subtreeRoot); + continue; + } + break; + } + } + + // TODO (Offscreen) Check: subtreeFlags & LayoutStatic + if (firstChild !== null) { + firstChild.return = fiber; + nextEffect = firstChild; + } else { + disappearLayoutEffects_complete(subtreeRoot); + } + } +} + +function disappearLayoutEffects_complete(subtreeRoot: Fiber) { + while (nextEffect !== null) { + const fiber = nextEffect; + + if (fiber === subtreeRoot) { + nextEffect = null; + return; + } + + const sibling = fiber.sibling; + if (sibling !== null) { + sibling.return = fiber.return; + nextEffect = sibling; + return; + } + + nextEffect = fiber.return; + } +} + function reappearLayoutEffects_begin(subtreeRoot: Fiber) { while (nextEffect !== null) { const fiber = nextEffect; @@ -2397,7 +2463,9 @@ function reappearLayoutEffects_begin(subtreeRoot: Fiber) { // TODO (Offscreen) Check: subtreeFlags & LayoutStatic if (firstChild !== null) { - ensureCorrectReturnPointer(firstChild, fiber); + // This node may have been reused from a previous render, so we can't + // assume its return pointer is correct. + firstChild.return = fiber; nextEffect = firstChild; } else { reappearLayoutEffects_complete(subtreeRoot); @@ -2426,7 +2494,9 @@ function reappearLayoutEffects_complete(subtreeRoot: Fiber) { const sibling = fiber.sibling; if (sibling !== null) { - ensureCorrectReturnPointer(sibling, fiber.return); + // This node may have been reused from a previous render, so we can't + // assume its return pointer is correct. + sibling.return = fiber.return; nextEffect = sibling; return; } diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js index e2bbc30998a77..327e72fdc9724 100644 --- a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js +++ b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js @@ -220,7 +220,6 @@ describe('ReactOffscreen', () => { // @gate experimental || www // @gate enableSuspenseLayoutEffectSemantics - // @gate !persistent it('mounts/unmounts layout effects when visibility changes (starting visible)', async () => { function Child({text}) { useLayoutEffect(() => { @@ -271,7 +270,6 @@ describe('ReactOffscreen', () => { // @gate experimental || www // @gate enableSuspenseLayoutEffectSemantics - // @gate !persistent it('mounts/unmounts layout effects when visibility changes (starting hidden)', async () => { function Child({text}) { useLayoutEffect(() => { diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js index fa16b83405114..6cb3c60971538 100644 --- a/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js +++ b/packages/react-reconciler/src/__tests__/ReactSuspenseEffectsSemantics-test.js @@ -567,7 +567,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should be destroyed and recreated for function components', async () => { function App({children = null}) { Scheduler.unstable_yieldValue('App render'); @@ -697,7 +696,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should be destroyed and recreated for class components', async () => { class ClassText extends React.Component { componentDidMount() { @@ -843,7 +841,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should be destroyed and recreated when nested below host components', async () => { function App({children = null}) { Scheduler.unstable_yieldValue('App render'); @@ -954,7 +951,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should be destroyed and recreated even if there is a bailout because of memoization', async () => { const MemoizedText = React.memo(Text, () => true); @@ -1071,7 +1067,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should respect nested suspense boundaries', async () => { function App({innerChildren = null, outerChildren = null}) { return ( @@ -1295,7 +1290,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should show nested host nodes if multiple boundaries resolve at the same time', async () => { function App({innerChildren = null, outerChildren = null}) { return ( @@ -1406,7 +1400,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should be cleaned up inside of a fallback that suspends', async () => { function App({fallbackChildren = null, outerChildren = null}) { return ( @@ -1550,7 +1543,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should be cleaned up inside of a fallback that suspends (alternate)', async () => { function App({fallbackChildren = null, outerChildren = null}) { return ( @@ -1671,7 +1663,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should be cleaned up deeper inside of a subtree that suspends', async () => { function ConditionalSuspense({shouldSuspend}) { if (shouldSuspend) { @@ -1755,7 +1746,6 @@ describe('ReactSuspenseEffectsSemantics', () => { describe('that throw errors', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('are properly handled for componentDidMount', async () => { let componentDidMountShouldThrow = false; @@ -1895,7 +1885,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('are properly handled for componentWillUnmount', async () => { class ThrowsInWillUnmount extends React.Component { componentDidMount() { @@ -2009,7 +1998,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent // @gate replayFailedUnitOfWorkWithInvokeGuardedCallback it('are properly handled for layout effect creation', async () => { let useLayoutEffectShouldThrow = false; @@ -2150,7 +2138,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent // @gate replayFailedUnitOfWorkWithInvokeGuardedCallback it('are properly handled for layout effect descruction', async () => { function ThrowsInLayoutEffectDestroy() { @@ -2263,7 +2250,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should be only destroy layout effects once if a tree suspends in multiple places', async () => { class ClassText extends React.Component { componentDidMount() { @@ -2403,7 +2389,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should be only destroy layout effects once if a component suspends multiple times', async () => { class ClassText extends React.Component { componentDidMount() { @@ -2688,7 +2673,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should be cleared and reset for host components', async () => { function App({children}) { Scheduler.unstable_yieldValue(`App render`); @@ -2786,7 +2770,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should be cleared and reset for class components', async () => { class ClassComponent extends React.Component { render() { @@ -2888,7 +2871,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should be cleared and reset for function components with useImperativeHandle', async () => { const FunctionComponent = React.forwardRef((props, ref) => { Scheduler.unstable_yieldValue('FunctionComponent render'); @@ -2994,7 +2976,6 @@ describe('ReactSuspenseEffectsSemantics', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent it('should not reset for user-managed values', async () => { function RefChecker({forwardedRef}) { Scheduler.unstable_yieldValue(`RefChecker render`); @@ -3093,7 +3074,6 @@ describe('ReactSuspenseEffectsSemantics', () => { describe('that throw errors', () => { // @gate enableSuspenseLayoutEffectSemantics // @gate enableCache - // @gate !persistent // @gate replayFailedUnitOfWorkWithInvokeGuardedCallback it('are properly handled in ref callbacks', async () => { let useRefCallbackShouldThrow = false;