diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js index e89eebb49157..15515672747a 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.internal.js @@ -9,7 +9,7 @@ 'use strict'; -const React = require('react'); +let React; let ReactFeatureFlags = require('shared/ReactFeatureFlags'); let ReactDOM; @@ -26,6 +26,7 @@ describe('ReactDOMFiberAsync', () => { beforeEach(() => { jest.resetModules(); container = document.createElement('div'); + React = require('react'); ReactDOM = require('react-dom'); Scheduler = require('scheduler'); @@ -587,6 +588,39 @@ describe('ReactDOMFiberAsync', () => { ); }); + it('regression test: does not drop passive effects across roots (#17066)', () => { + const {useState, useEffect} = React; + + function App({label}) { + const [step, setStep] = useState(0); + useEffect( + () => { + if (step < 3) { + setStep(step + 1); + } + }, + [step], + ); + + // The component should keep re-rendering itself until `step` is 3. + return step === 3 ? 'Finished' : 'Unresolved'; + } + + const containerA = document.createElement('div'); + const containerB = document.createElement('div'); + const containerC = document.createElement('div'); + + ReactDOM.render(, containerA); + ReactDOM.render(, containerB); + ReactDOM.render(, containerC); + + Scheduler.unstable_flushAll(); + + expect(containerA.textContent).toEqual('Finished'); + expect(containerB.textContent).toEqual('Finished'); + expect(containerC.textContent).toEqual('Finished'); + }); + describe('createBlockingRoot', () => { it.experimental('updates flush without yielding in the next event', () => { const root = ReactDOM.createBlockingRoot(container); diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index 85d678dec501..954d646bd789 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -1713,7 +1713,15 @@ function commitRoot(root) { } function commitRootImpl(root, renderPriorityLevel) { - flushPassiveEffects(); + do { + // `flushPassiveEffects` will call `flushSyncUpdateQueue` at the end, which + // means `flushPassiveEffects` will sometimes result in additional + // passive effects. So we need to keep flushing in a loop until there are + // no more pending effects. + // TODO: Might be better if `flushPassiveEffects` did not automatically + // flush synchronous work at the end, to avoid factoring hazards like this. + flushPassiveEffects(); + } while (rootWithPendingPassiveEffects !== null); flushRenderPhaseStrictModeWarningsInDEV(); invariant(