From b44e4d0d0bc309626d37e80ce9d0ef576018cb44 Mon Sep 17 00:00:00 2001 From: Andrew Clark Date: Thu, 8 Apr 2021 20:45:35 -0500 Subject: [PATCH] Fix: Passive effect updates are never sync (#21215) I screwed this up in #21082. Got confused by the < versus > thing again. The helper functions are annoying, too, because I always forget the intended order of the arguments. But they're still helpful because when we refactor the type we only have the change the logic in one place. Added a regression test. --- .../src/__tests__/ReactUpdatePriority-test.js | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 packages/react-reconciler/src/__tests__/ReactUpdatePriority-test.js 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']); + }); +});