diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js index c1dc8a33cceb..a8475a024aab 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js @@ -638,6 +638,61 @@ describe('ReactDOMFiberAsync', () => { }); }); + it('calls effect cleanup when unmounting in a transition inside StrictMode', async () => { + let handlerCalls = 0; + let cleanupCalls = 0; + let setShow; + + function Child() { + React.useEffect(() => { + const onResize = () => { + handlerCalls++; + }; + window.addEventListener('resize', onResize); + return () => { + cleanupCalls++; + window.removeEventListener('resize', onResize); + }; + }, []); + + return
Child
; + } + + function App() { + const [show, _setShow] = React.useState(true); + setShow = _setShow; + return <>{show ? : null}; + } + + const root = ReactDOMClient.createRoot(container); + await act(async () => { + root.render( + + + , + ); + }); + + await act(async () => { + React.startTransition(() => { + setShow(false); + }); + }); + + // Cleanup should remove the listener before any post-unmount event. + window.dispatchEvent(new Event('resize')); + expect(handlerCalls).toBe(0); + if (__DEV__) { + expect(cleanupCalls).toBe(2); + } else { + expect(cleanupCalls).toBe(1); + } + + await act(async () => { + root.unmount(); + }); + }); + it('Should not flush transition lanes if there is no transition scheduled in popState', async () => { let setHasNavigated; function App() { diff --git a/packages/react-reconciler/src/__tests__/ActivityStrictMode-test.js b/packages/react-reconciler/src/__tests__/ActivityStrictMode-test.js index 887ac7d1ce84..be59af513eac 100644 --- a/packages/react-reconciler/src/__tests__/ActivityStrictMode-test.js +++ b/packages/react-reconciler/src/__tests__/ActivityStrictMode-test.js @@ -127,6 +127,35 @@ describe('Activity StrictMode', () => { ]); }); + // @gate __DEV__ + it('calls passive cleanup when unmounting in a transition', async () => { + const root = ReactNoop.createRoot(); + + await act(() => { + root.render( + + + + + , + ); + }); + + log = []; + + await act(() => { + React.startTransition(() => { + root.render( + + + , + ); + }); + }); + + expect(log).toContain('A: useEffect unmount'); + }); + it('should not cause infinite render loop when StrictMode is used with Suspense and synchronous set states', async () => { // This is a regression test, see https://github.com/facebook/react/pull/25179 for more details. function App() {