diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.new.js b/packages/react-reconciler/src/ReactFiberCommitWork.new.js
index 40c3bd2b231bc..f3fa8fbb21552 100644
--- a/packages/react-reconciler/src/ReactFiberCommitWork.new.js
+++ b/packages/react-reconciler/src/ReactFiberCommitWork.new.js
@@ -2880,7 +2880,8 @@ function commitMutationEffectsOnFiber(
}
if (isHidden) {
- if (!wasHidden) {
+ // Check if this is an update, and the tree was previously visible.
+ if (current !== null && !wasHidden) {
if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) {
// Disappear the layout effects of all the children
recursivelyTraverseDisappearLayoutEffects(offscreenBoundary);
diff --git a/packages/react-reconciler/src/ReactFiberCommitWork.old.js b/packages/react-reconciler/src/ReactFiberCommitWork.old.js
index 4f960961c5e33..ea1ff57f3d8db 100644
--- a/packages/react-reconciler/src/ReactFiberCommitWork.old.js
+++ b/packages/react-reconciler/src/ReactFiberCommitWork.old.js
@@ -2880,7 +2880,8 @@ function commitMutationEffectsOnFiber(
}
if (isHidden) {
- if (!wasHidden) {
+ // Check if this is an update, and the tree was previously visible.
+ if (current !== null && !wasHidden) {
if ((offscreenBoundary.mode & ConcurrentMode) !== NoMode) {
// Disappear the layout effects of all the children
recursivelyTraverseDisappearLayoutEffects(offscreenBoundary);
diff --git a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js
index 66bd613763960..f7ca087088a70 100644
--- a/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactOffscreen-test.js
@@ -268,6 +268,44 @@ describe('ReactOffscreen', () => {
expect(root).toMatchRenderedOutput();
});
+ // @gate enableOffscreen
+ it('nested offscreen does not call componentWillUnmount when hidden', async () => {
+ // This is a bug that appeared during production test of .
+ // It is a very specific scenario with nested Offscreens. The inner offscreen
+ // goes from visible to hidden in synchronous update.
+ class ClassComponent extends React.Component {
+ render() {
+ return ;
+ }
+
+ componentWillUnmount() {
+ Scheduler.unstable_yieldValue('componentWillUnmount');
+ }
+ }
+
+ function App() {
+ const [isVisible, setIsVisible] = React.useState(true);
+
+ if (isVisible === true) {
+ setIsVisible(false);
+ }
+
+ return (
+
+
+
+
+
+ );
+ }
+
+ const root = ReactNoop.createRoot();
+ await act(async () => {
+ root.render();
+ });
+ expect(Scheduler).toHaveYielded(['Child']);
+ });
+
// @gate enableOffscreen
it('mounts/unmounts layout effects when visibility changes (starting hidden)', async () => {
function Child({text}) {