diff --git a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
index 3ed6e4da23440..6ddf0f0bbe993 100644
--- a/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
@@ -1632,7 +1632,7 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('ba');
});
- it('does not destroy layout effects twice', async () => {
+ it('does not destroy layout effects twice when hidden child is removed', async () => {
function ChildA({label}) {
React.useLayoutEffect(() => {
Scheduler.unstable_yieldValue('Did mount: ' + label);
@@ -1686,7 +1686,7 @@ describe('ReactLazy', () => {
expect(root).toMatchRenderedOutput('B');
});
- it('does not call componentWillUnmount twice', async () => {
+ it('does not call componentWillUnmount twice when hidden child is removed', async () => {
class ChildA extends React.Component {
componentDidMount() {
Scheduler.unstable_yieldValue('Did mount: ' + this.props.label);
@@ -1743,4 +1743,120 @@ describe('ReactLazy', () => {
expect(Scheduler).toFlushAndYield(['B', 'Did mount: B']);
expect(root).toMatchRenderedOutput('B');
});
+
+ it('does not destroy layout effects twice when parent suspense is removed', async () => {
+ function ChildA({label}) {
+ React.useLayoutEffect(() => {
+ Scheduler.unstable_yieldValue('Did mount: ' + label);
+ return () => {
+ Scheduler.unstable_yieldValue('Will unmount: ' + label);
+ };
+ }, []);
+ return ;
+ }
+ function ChildB({label}) {
+ React.useLayoutEffect(() => {
+ Scheduler.unstable_yieldValue('Did mount: ' + label);
+ return () => {
+ Scheduler.unstable_yieldValue('Will unmount: ' + label);
+ };
+ }, []);
+ return ;
+ }
+ const LazyChildA = lazy(() => fakeImport(ChildA));
+ const LazyChildB = lazy(() => fakeImport(ChildB));
+
+ function Parent({swap}) {
+ return (
+ }>
+ {swap ? : }
+
+ );
+ }
+
+ const root = ReactTestRenderer.create(, {
+ unstable_isConcurrent: true,
+ });
+
+ expect(Scheduler).toFlushAndYield(['Loading...']);
+
+ await LazyChildA;
+ expect(Scheduler).toFlushAndYield(['A', 'Did mount: A']);
+ expect(root).toMatchRenderedOutput('A');
+
+ // Swap the position of A and B
+ root.unstable_flushSync(() => {
+ root.update();
+ });
+ expect(Scheduler).toHaveYielded(['Loading...', 'Will unmount: A']);
+ expect(root).toMatchRenderedOutput('Loading...');
+
+ // Destroy the whole tree, including the hidden A
+ root.unstable_flushSync(() => {
+ root.update(
Hello
);
+ });
+ expect(Scheduler).toFlushAndYield([]);
+ expect(root).toMatchRenderedOutput(Hello
);
+ });
+
+ it('does not call componentWillUnmount twice when parent suspense is removed', async () => {
+ class ChildA extends React.Component {
+ componentDidMount() {
+ Scheduler.unstable_yieldValue('Did mount: ' + this.props.label);
+ }
+ componentWillUnmount() {
+ Scheduler.unstable_yieldValue('Will unmount: ' + this.props.label);
+ }
+ render() {
+ return ;
+ }
+ }
+
+ class ChildB extends React.Component {
+ componentDidMount() {
+ Scheduler.unstable_yieldValue('Did mount: ' + this.props.label);
+ }
+ componentWillUnmount() {
+ Scheduler.unstable_yieldValue('Will unmount: ' + this.props.label);
+ }
+ render() {
+ return ;
+ }
+ }
+
+ const LazyChildA = lazy(() => fakeImport(ChildA));
+ const LazyChildB = lazy(() => fakeImport(ChildB));
+
+ function Parent({swap}) {
+ return (
+ }>
+ {swap ? : }
+
+ );
+ }
+
+ const root = ReactTestRenderer.create(, {
+ unstable_isConcurrent: true,
+ });
+
+ expect(Scheduler).toFlushAndYield(['Loading...']);
+
+ await LazyChildA;
+ expect(Scheduler).toFlushAndYield(['A', 'Did mount: A']);
+ expect(root).toMatchRenderedOutput('A');
+
+ // Swap the position of A and B
+ root.unstable_flushSync(() => {
+ root.update();
+ });
+ expect(Scheduler).toHaveYielded(['Loading...', 'Will unmount: A']);
+ expect(root).toMatchRenderedOutput('Loading...');
+
+ // Destroy the whole tree, including the hidden A
+ root.unstable_flushSync(() => {
+ root.update(Hello
);
+ });
+ expect(Scheduler).toFlushAndYield(['']);
+ expect(root).toMatchRenderedOutput(Hello
);
+ });
});