diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.new.js b/packages/react-reconciler/src/ReactFiberBeginWork.new.js
index 2d3841d2e0e43..fa27d3dcad74e 100644
--- a/packages/react-reconciler/src/ReactFiberBeginWork.new.js
+++ b/packages/react-reconciler/src/ReactFiberBeginWork.new.js
@@ -64,6 +64,7 @@ import {
Ref,
Deletion,
ForceUpdateForLegacySuspense,
+ StaticMask,
} from './ReactFiberFlags';
import ReactSharedInternals from 'shared/ReactSharedInternals';
import {
@@ -2131,6 +2132,12 @@ function updateSuspenseFallbackChildren(
currentPrimaryChildFragment,
primaryChildProps,
);
+
+ // Since we're reusing a current tree, we need to reuse the flags, too.
+ // (We don't do this in legacy mode, because in legacy mode we don't re-use
+ // the current tree; see previous branch.)
+ primaryChildFragment.subtreeFlags =
+ currentPrimaryChildFragment.subtreeFlags & StaticMask;
}
let fallbackChildFragment;
if (currentFallbackChildFragment !== null) {
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
index c10772eaa16e5..b6ed0d2be4d40 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
@@ -3956,4 +3956,101 @@ describe('ReactSuspenseWithNoopRenderer', () => {
>,
);
});
+
+ it('should fire effect clean-up when deleting suspended tree', async () => {
+ const {useEffect} = React;
+
+ function App({show}) {
+ return (
+ }>
+
+ {show && }
+
+ );
+ }
+
+ function Child() {
+ useEffect(() => {
+ Scheduler.unstable_yieldValue('Mount Child');
+ return () => {
+ Scheduler.unstable_yieldValue('Unmount Child');
+ };
+ }, []);
+ return ;
+ }
+
+ const root = ReactNoop.createRoot();
+
+ await ReactNoop.act(async () => {
+ root.render();
+ });
+ expect(Scheduler).toHaveYielded(['Mount Child']);
+ expect(root).toMatchRenderedOutput();
+
+ await ReactNoop.act(async () => {
+ root.render();
+ });
+ // TODO: `act` should have already flushed the placeholder, so this
+ // runAllTimers call should be unnecessary.
+ jest.runAllTimers();
+ expect(Scheduler).toHaveYielded(['Suspend! [Async]', 'Loading...']);
+ expect(root).toMatchRenderedOutput(
+ <>
+
+
+ >,
+ );
+
+ await ReactNoop.act(async () => {
+ root.render(null);
+ });
+ expect(Scheduler).toHaveYielded(['Unmount Child']);
+ });
+
+ it('should fire effect clean-up when deleting suspended tree (legacy)', async () => {
+ const {useEffect} = React;
+
+ function App({show}) {
+ return (
+ }>
+
+ {show && }
+
+ );
+ }
+
+ function Child() {
+ useEffect(() => {
+ Scheduler.unstable_yieldValue('Mount Child');
+ return () => {
+ Scheduler.unstable_yieldValue('Unmount Child');
+ };
+ }, []);
+ return ;
+ }
+
+ const root = ReactNoop.createLegacyRoot();
+
+ await ReactNoop.act(async () => {
+ root.render();
+ });
+ expect(Scheduler).toHaveYielded(['Mount Child']);
+ expect(root).toMatchRenderedOutput();
+
+ await ReactNoop.act(async () => {
+ root.render();
+ });
+ expect(Scheduler).toHaveYielded(['Suspend! [Async]', 'Loading...']);
+ expect(root).toMatchRenderedOutput(
+ <>
+
+
+ >,
+ );
+
+ await ReactNoop.act(async () => {
+ root.render(null);
+ });
+ expect(Scheduler).toHaveYielded(['Unmount Child']);
+ });
});