diff --git a/packages/react-dom/src/__tests__/ReactDOMHooks-test.js b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js index 896c8c0acac01..aff3003fc0ceb 100644 --- a/packages/react-dom/src/__tests__/ReactDOMHooks-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMHooks-test.js @@ -53,32 +53,23 @@ describe('ReactDOMHooks', () => { return 3 * n; } - // we explicitly catch the missing act() warnings - // to simulate this tricky repro - expect(() => { - ReactDOM.render(, container); - expect(container.textContent).toBe('1'); - expect(container2.textContent).toBe(''); - expect(container3.textContent).toBe(''); - Scheduler.unstable_flushAll(); - expect(container.textContent).toBe('1'); - expect(container2.textContent).toBe('2'); - expect(container3.textContent).toBe('3'); - - ReactDOM.render(, container); - expect(container.textContent).toBe('2'); - expect(container2.textContent).toBe('2'); // Not flushed yet - expect(container3.textContent).toBe('3'); // Not flushed yet - Scheduler.unstable_flushAll(); - expect(container.textContent).toBe('2'); - expect(container2.textContent).toBe('4'); - expect(container3.textContent).toBe('6'); - }).toWarnDev([ - 'An update to Example1 ran an effect', - 'An update to Example2 ran an effect', - 'An update to Example1 ran an effect', - 'An update to Example2 ran an effect', - ]); + ReactDOM.render(, container); + expect(container.textContent).toBe('1'); + expect(container2.textContent).toBe(''); + expect(container3.textContent).toBe(''); + Scheduler.unstable_flushAll(); + expect(container.textContent).toBe('1'); + expect(container2.textContent).toBe('2'); + expect(container3.textContent).toBe('3'); + + ReactDOM.render(, container); + expect(container.textContent).toBe('2'); + expect(container2.textContent).toBe('2'); // Not flushed yet + expect(container3.textContent).toBe('3'); // Not flushed yet + Scheduler.unstable_flushAll(); + expect(container.textContent).toBe('2'); + expect(container2.textContent).toBe('4'); + expect(container3.textContent).toBe('6'); }); it('should not bail out when an update is scheduled from within an event handler', () => { diff --git a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js index fb1027a306289..827b3aa2f1855 100644 --- a/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js +++ b/packages/react-dom/src/__tests__/ReactTestUtilsAct-test.js @@ -48,6 +48,73 @@ describe('ReactTestUtils.act()', () => { ReactDOM.unmountComponentAtNode(dom); } runActTests('legacy sync mode', renderSync, unmountSync); + + // and then in batched mode + let batchedRoot; + function renderBatched(el, dom) { + batchedRoot = ReactDOM.unstable_createSyncRoot(dom); + batchedRoot.render(el); + } + function unmountBatched(dom) { + if (batchedRoot !== null) { + batchedRoot.unmount(); + batchedRoot = null; + } + } + runActTests('batched mode', renderBatched, unmountBatched); + + describe('unacted effects', () => { + function App() { + React.useEffect(() => {}, []); + return null; + } + + it('does not warn in legacy sync mode', () => { + expect(() => { + ReactDOM.render(, document.createElement('div')); + }).toWarnDev([]); + }); + + it('warns in strict mode', () => { + expect(() => { + ReactDOM.render( + + + , + document.createElement('div'), + ); + }).toWarnDev([ + 'An update to App ran an effect, but was not wrapped in act(...)', + 'An update to App ran an effect, but was not wrapped in act(...)', + ]); + }); + + it('warns in batched mode', () => { + expect(() => { + const root = ReactDOM.unstable_createSyncRoot( + document.createElement('div'), + ); + root.render(); + Scheduler.unstable_flushAll(); + }).toWarnDev([ + 'An update to App ran an effect, but was not wrapped in act(...)', + 'An update to App ran an effect, but was not wrapped in act(...)', + ]); + }); + + it('warns in concurrent mode', () => { + expect(() => { + const root = ReactDOM.unstable_createRoot( + document.createElement('div'), + ); + root.render(); + Scheduler.unstable_flushAll(); + }).toWarnDev([ + 'An update to App ran an effect, but was not wrapped in act(...)', + 'An update to App ran an effect, but was not wrapped in act(...)', + ]); + }); + }); }); function runActTests(label, render, unmount) { diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 1c5d14651fc27..bdd43849c3042 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -61,6 +61,7 @@ import { import {createWorkInProgress, assignFiberPropertiesInDEV} from './ReactFiber'; import { NoMode, + StrictMode, ProfileMode, BatchedMode, ConcurrentMode, @@ -2453,6 +2454,10 @@ export function warnIfNotCurrentlyActingEffectsInDEV(fiber: Fiber): void { if (__DEV__) { if ( warnsIfNotActing === true && + (fiber.mode & StrictMode || + fiber.mode & ProfileMode || + fiber.mode & BatchedMode || + fiber.mode & ConcurrentMode) && IsSomeRendererActing.current === false && IsThisRendererActing.current === false ) { diff --git a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js index e0022d0a70c22..eff2904158a72 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js @@ -1806,7 +1806,6 @@ describe('ReactHooks', () => { globalListener(); globalListener(); }).toWarnDev([ - 'An update to C ran an effect', 'An update to C inside a test was not wrapped in act', 'An update to C inside a test was not wrapped in act', // Note: should *not* warn about updates on unmounted component.