Skip to content

Commit b943aeb

Browse files
authored
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.
1 parent d389c54 commit b943aeb

File tree

5 files changed

+101
-10
lines changed

5 files changed

+101
-10
lines changed

packages/react-reconciler/src/ReactEventPriorities.new.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ export function higherEventPriority(
5353
return a !== 0 && a < b ? a : b;
5454
}
5555

56+
export function lowerEventPriority(
57+
a: EventPriority,
58+
b: EventPriority,
59+
): EventPriority {
60+
return a === 0 || a > b ? a : b;
61+
}
62+
5663
export function isHigherEventPriority(
5764
a: EventPriority,
5865
b: EventPriority,

packages/react-reconciler/src/ReactEventPriorities.old.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ export function higherEventPriority(
5353
return a !== 0 && a < b ? a : b;
5454
}
5555

56+
export function lowerEventPriority(
57+
a: EventPriority,
58+
b: EventPriority,
59+
): EventPriority {
60+
return a === 0 || a > b ? a : b;
61+
}
62+
5663
export function isHigherEventPriority(
5764
a: EventPriority,
5865
b: EventPriority,

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ import {
167167
IdleEventPriority,
168168
getCurrentUpdatePriority,
169169
setCurrentUpdatePriority,
170-
higherEventPriority,
170+
lowerEventPriority,
171171
lanesToEventPriority,
172172
} from './ReactEventPriorities.new';
173173
import {requestCurrentTransition, NoTransition} from './ReactFiberTransition';
@@ -2049,10 +2049,8 @@ function commitRootImpl(root, renderPriorityLevel) {
20492049
export function flushPassiveEffects(): boolean {
20502050
// Returns whether passive effects were flushed.
20512051
if (pendingPassiveEffectsLanes !== NoLanes) {
2052-
const priority = higherEventPriority(
2053-
DefaultEventPriority,
2054-
lanesToEventPriority(pendingPassiveEffectsLanes),
2055-
);
2052+
const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
2053+
const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
20562054
const prevTransition = ReactCurrentBatchConfig.transition;
20572055
const previousPriority = getCurrentUpdatePriority();
20582056
try {

packages/react-reconciler/src/ReactFiberWorkLoop.old.js

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ import {
167167
IdleEventPriority,
168168
getCurrentUpdatePriority,
169169
setCurrentUpdatePriority,
170-
higherEventPriority,
170+
lowerEventPriority,
171171
lanesToEventPriority,
172172
} from './ReactEventPriorities.old';
173173
import {requestCurrentTransition, NoTransition} from './ReactFiberTransition';
@@ -2049,10 +2049,8 @@ function commitRootImpl(root, renderPriorityLevel) {
20492049
export function flushPassiveEffects(): boolean {
20502050
// Returns whether passive effects were flushed.
20512051
if (pendingPassiveEffectsLanes !== NoLanes) {
2052-
const priority = higherEventPriority(
2053-
DefaultEventPriority,
2054-
lanesToEventPriority(pendingPassiveEffectsLanes),
2055-
);
2052+
const renderPriority = lanesToEventPriority(pendingPassiveEffectsLanes);
2053+
const priority = lowerEventPriority(DefaultEventPriority, renderPriority);
20562054
const prevTransition = ReactCurrentBatchConfig.transition;
20572055
const previousPriority = getCurrentUpdatePriority();
20582056
try {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
let React;
2+
let ReactNoop;
3+
let Scheduler;
4+
let useState;
5+
let useEffect;
6+
7+
describe('ReactUpdatePriority', () => {
8+
beforeEach(() => {
9+
jest.resetModules();
10+
11+
React = require('react');
12+
ReactNoop = require('react-noop-renderer');
13+
Scheduler = require('scheduler');
14+
useState = React.useState;
15+
useEffect = React.useEffect;
16+
});
17+
18+
function Text({text}) {
19+
Scheduler.unstable_yieldValue(text);
20+
return text;
21+
}
22+
23+
test('setState inside passive effect triggered by sync update should have default priority', async () => {
24+
const root = ReactNoop.createRoot();
25+
26+
function App() {
27+
const [state, setState] = useState(1);
28+
useEffect(() => {
29+
setState(2);
30+
}, []);
31+
return <Text text={state} />;
32+
}
33+
34+
await ReactNoop.act(async () => {
35+
ReactNoop.flushSync(() => {
36+
root.render(<App />);
37+
});
38+
// Should not have flushed the effect update yet
39+
expect(Scheduler).toHaveYielded([1]);
40+
});
41+
expect(Scheduler).toHaveYielded([2]);
42+
});
43+
44+
test('setState inside passive effect triggered by idle update should have idle priority', async () => {
45+
const root = ReactNoop.createRoot();
46+
47+
let setDefaultState;
48+
function App() {
49+
const [idleState, setIdleState] = useState(1);
50+
const [defaultState, _setDetaultState] = useState(1);
51+
setDefaultState = _setDetaultState;
52+
useEffect(() => {
53+
Scheduler.unstable_yieldValue('Idle update');
54+
setIdleState(2);
55+
}, []);
56+
return <Text text={`Idle: ${idleState}, Default: ${defaultState}`} />;
57+
}
58+
59+
await ReactNoop.act(async () => {
60+
ReactNoop.idleUpdates(() => {
61+
root.render(<App />);
62+
});
63+
// Should not have flushed the effect update yet
64+
expect(Scheduler).toFlushUntilNextPaint(['Idle: 1, Default: 1']);
65+
66+
// Schedule another update at default priority
67+
setDefaultState(2);
68+
69+
// The default update flushes first, because
70+
expect(Scheduler).toFlushUntilNextPaint([
71+
// Idle update is scheduled
72+
'Idle update',
73+
74+
// The default update flushes first, without including the idle update
75+
'Idle: 1, Default: 2',
76+
]);
77+
});
78+
// Now the idle update has flushed
79+
expect(Scheduler).toHaveYielded(['Idle: 2, Default: 2']);
80+
});
81+
});

0 commit comments

Comments
 (0)