diff --git a/packages/react-reconciler/src/ReactEventPriorities.new.js b/packages/react-reconciler/src/ReactEventPriorities.new.js index 2c1b9c249fa93..96225c19b3f11 100644 --- a/packages/react-reconciler/src/ReactEventPriorities.new.js +++ b/packages/react-reconciler/src/ReactEventPriorities.new.js @@ -53,6 +53,13 @@ export function higherEventPriority( return a !== 0 && a < b ? a : b; } +export function lowerEventPriority( + a: EventPriority, + b: EventPriority, +): EventPriority { + return a === 0 || a > b ? a : b; +} + export function isHigherEventPriority( a: EventPriority, b: EventPriority, diff --git a/packages/react-reconciler/src/ReactEventPriorities.old.js b/packages/react-reconciler/src/ReactEventPriorities.old.js index 7e1b0acb7a9a6..8db74b9397a83 100644 --- a/packages/react-reconciler/src/ReactEventPriorities.old.js +++ b/packages/react-reconciler/src/ReactEventPriorities.old.js @@ -53,6 +53,13 @@ export function higherEventPriority( return a !== 0 && a < b ? a : b; } +export function lowerEventPriority( + a: EventPriority, + b: EventPriority, +): EventPriority { + return a === 0 || a > b ? a : b; +} + export function isHigherEventPriority( a: EventPriority, b: EventPriority, diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index e86cb6777c286..0792ac2b29647 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -163,7 +163,7 @@ import { DefaultEventPriority, getCurrentUpdatePriority, setCurrentUpdatePriority, - higherEventPriority, + lowerEventPriority, lanesToEventPriority, } from './ReactEventPriorities.new'; import {requestCurrentTransition, NoTransition} from './ReactFiberTransition'; @@ -2008,10 +2008,8 @@ function commitRootImpl(root, renderPriorityLevel) { export function flushPassiveEffects(): boolean { // Returns whether passive effects were flushed. if (pendingPassiveEffectsLanes !== NoLanes) { - const priority = higherEventPriority( - DefaultEventPriority, - lanesToEventPriority(pendingPassiveEffectsLanes), - ); + const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes); + const priority = lowerEventPriority(DefaultEventPriority, renderPriority); const previousPriority = getCurrentUpdatePriority(); try { setCurrentUpdatePriority(priority); diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index 7d452fe2699f8..760b971767b49 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -163,7 +163,7 @@ import { DefaultEventPriority, getCurrentUpdatePriority, setCurrentUpdatePriority, - higherEventPriority, + lowerEventPriority, lanesToEventPriority, } from './ReactEventPriorities.old'; import {requestCurrentTransition, NoTransition} from './ReactFiberTransition'; @@ -2008,10 +2008,8 @@ function commitRootImpl(root, renderPriorityLevel) { export function flushPassiveEffects(): boolean { // Returns whether passive effects were flushed. if (pendingPassiveEffectsLanes !== NoLanes) { - const priority = higherEventPriority( - DefaultEventPriority, - lanesToEventPriority(pendingPassiveEffectsLanes), - ); + const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes); + const priority = lowerEventPriority(DefaultEventPriority, renderPriority); const previousPriority = getCurrentUpdatePriority(); try { setCurrentUpdatePriority(priority); diff --git a/packages/react-reconciler/src/__tests__/ReactUpdatePriority-test.js b/packages/react-reconciler/src/__tests__/ReactUpdatePriority-test.js new file mode 100644 index 0000000000000..0aee7df66df4a --- /dev/null +++ b/packages/react-reconciler/src/__tests__/ReactUpdatePriority-test.js @@ -0,0 +1,81 @@ +let React; +let ReactNoop; +let Scheduler; +let useState; +let useEffect; + +describe('ReactUpdatePriority', () => { + beforeEach(() => { + jest.resetModules(); + + React = require('react'); + ReactNoop = require('react-noop-renderer'); + Scheduler = require('scheduler'); + useState = React.useState; + useEffect = React.useEffect; + }); + + function Text({text}) { + Scheduler.unstable_yieldValue(text); + return text; + } + + test('setState inside passive effect triggered by sync update should have default priority', async () => { + const root = ReactNoop.createRoot(); + + function App() { + const [state, setState] = useState(1); + useEffect(() => { + setState(2); + }, []); + return ; + } + + await ReactNoop.act(async () => { + ReactNoop.flushSync(() => { + root.render(); + }); + // Should not have flushed the effect update yet + expect(Scheduler).toHaveYielded([1]); + }); + expect(Scheduler).toHaveYielded([2]); + }); + + test('setState inside passive effect triggered by idle update should have idle priority', async () => { + const root = ReactNoop.createRoot(); + + let setDefaultState; + function App() { + const [idleState, setIdleState] = useState(1); + const [defaultState, _setDetaultState] = useState(1); + setDefaultState = _setDetaultState; + useEffect(() => { + Scheduler.unstable_yieldValue('Idle update'); + setIdleState(2); + }, []); + return ; + } + + await ReactNoop.act(async () => { + ReactNoop.idleUpdates(() => { + root.render(); + }); + // Should not have flushed the effect update yet + expect(Scheduler).toFlushUntilNextPaint(['Idle: 1, Default: 1']); + + // Schedule another update at default priority + setDefaultState(2); + + // The default update flushes first, because + expect(Scheduler).toFlushUntilNextPaint([ + // Idle update is scheduled + 'Idle update', + + // The default update flushes first, without including the idle update + 'Idle: 1, Default: 2', + ]); + }); + // Now the idle update has flushed + expect(Scheduler).toHaveYielded(['Idle: 2, Default: 2']); + }); +});