diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.js b/packages/react-reconciler/src/ReactFiberWorkLoop.js index dad5c82eeb106..3775232f196b9 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.js @@ -1497,14 +1497,6 @@ function commitRoot(root) { ImmediatePriority, commitRootImpl.bind(null, root, renderPriorityLevel), ); - // If there are passive effects, schedule a callback to flush them. This goes - // outside commitRootImpl so that it inherits the priority of the render. - if (rootWithPendingPassiveEffects !== null) { - scheduleCallback(NormalPriority, () => { - flushPassiveEffects(); - return null; - }); - } return null; } @@ -1858,6 +1850,17 @@ function commitMutationEffects(root: FiberRoot, renderPriorityLevel) { } } + if (effectTag & Passive) { + // If there are passive effects, schedule a callback to flush them. + if (!rootDoesHavePassiveEffects) { + rootDoesHavePassiveEffects = true; + scheduleCallback(NormalPriority, () => { + flushPassiveEffects(); + return null; + }); + } + } + // The following switch statement is only concerned about placement, // updates, and deletions. To avoid needing to add a case for every possible // bitmap value, we remove the secondary effects from the effect tag and @@ -1943,10 +1946,6 @@ function commitLayoutEffects( commitAttachRef(nextEffect); } - if (effectTag & Passive) { - rootDoesHavePassiveEffects = true; - } - resetCurrentDebugFiberInDEV(); nextEffect = nextEffect.nextEffect; } diff --git a/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js index 17141304f1939..5fe90aac0e003 100644 --- a/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactSchedulerIntegration-test.internal.js @@ -265,6 +265,33 @@ describe('ReactSchedulerIntegration', () => { expect(Scheduler).toHaveYielded(['Effect clean-up priority: Idle']); }); + it('passive effects are called before Normal-pri scheduled in layout effects', async () => { + const {useEffect, useLayoutEffect} = React; + function Effects({step}) { + useLayoutEffect(() => { + Scheduler.unstable_yieldValue('Layout Effect'); + Scheduler.unstable_scheduleCallback(NormalPriority, () => + Scheduler.unstable_yieldValue( + 'Scheduled Normal Callback from Layout Effect', + ), + ); + }); + useEffect(() => { + Scheduler.unstable_yieldValue('Passive Effect'); + }); + return null; + } + await ReactNoop.act(async () => { + ReactNoop.render(); + }); + expect(Scheduler).toHaveYielded([ + 'Layout Effect', + 'Passive Effect', + // This callback should be scheduled after the passive effects. + 'Scheduled Normal Callback from Layout Effect', + ]); + }); + it('after completing a level of work, infers priority of the next batch based on its expiration time', () => { function App({label}) { Scheduler.unstable_yieldValue(