Skip to content

Commit 720eb03

Browse files
committed
Fix: Passive effect updates are never sync
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 720eb03

File tree

5 files changed

+103
-10
lines changed

5 files changed

+103
-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: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
let React;
2+
let ReactNoop;
3+
let Scheduler;
4+
let useState;
5+
let useEffect;
6+
let startTransition;
7+
8+
describe('ReactUpdatePriority', () => {
9+
beforeEach(() => {
10+
jest.resetModules();
11+
12+
React = require('react');
13+
ReactNoop = require('react-noop-renderer');
14+
Scheduler = require('scheduler');
15+
useState = React.useState;
16+
useEffect = React.useEffect;
17+
startTransition = React.unstable_startTransition;
18+
});
19+
20+
function Text({text}) {
21+
Scheduler.unstable_yieldValue(text);
22+
return text;
23+
}
24+
25+
test('setState inside passive effect triggered by sync update should have default priority', async () => {
26+
const root = ReactNoop.createRoot();
27+
28+
function App() {
29+
const [state, setState] = useState(1);
30+
useEffect(() => {
31+
setState(2);
32+
}, []);
33+
return <Text text={state} />;
34+
}
35+
36+
await ReactNoop.act(async () => {
37+
ReactNoop.flushSync(() => {
38+
root.render(<App />);
39+
});
40+
// Should not have flushed the effect update yet
41+
expect(Scheduler).toHaveYielded([1]);
42+
});
43+
expect(Scheduler).toHaveYielded([2]);
44+
});
45+
46+
test('setState inside passive effect triggered by idle update should have idle priority', async () => {
47+
const root = ReactNoop.createRoot();
48+
49+
let setDefaultState;
50+
function App() {
51+
const [idleState, setIdleState] = useState(1);
52+
const [defaultState, _setDetaultState] = useState(1);
53+
setDefaultState = _setDetaultState;
54+
useEffect(() => {
55+
Scheduler.unstable_yieldValue('Idle update');
56+
setIdleState(2);
57+
}, []);
58+
return <Text text={`Idle: ${idleState}, Default: ${defaultState}`} />;
59+
}
60+
61+
await ReactNoop.act(async () => {
62+
ReactNoop.idleUpdates(() => {
63+
root.render(<App />);
64+
});
65+
// Should not have flushed the effect update yet
66+
expect(Scheduler).toFlushUntilNextPaint(['Idle: 1, Default: 1']);
67+
68+
// Schedule another update at default priority
69+
setDefaultState(2);
70+
71+
// The default update flushes first, because
72+
expect(Scheduler).toFlushUntilNextPaint([
73+
// Idle update is scheduled
74+
'Idle update',
75+
76+
// The default update flushes first, without including the idle update
77+
'Idle: 1, Default: 2',
78+
]);
79+
});
80+
// Now the idle update has flushed
81+
expect(Scheduler).toHaveYielded(['Idle: 2, Default: 2']);
82+
});
83+
});

0 commit comments

Comments
 (0)