Skip to content

Commit 3ae94e1

Browse files
authored
Fix ignored sync work in passive effects (facebook#14799)
* Fix ignored sync work in passive effects * Fix batching
1 parent f3a1495 commit 3ae94e1

File tree

2 files changed

+82
-1
lines changed

2 files changed

+82
-1
lines changed

packages/react-dom/src/__tests__/ReactDOMHooks-test.js

+78-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
let React;
1313
let ReactDOM;
1414

15-
describe('ReactDOMSuspensePlaceholder', () => {
15+
describe('ReactDOMHooks', () => {
1616
let container;
1717

1818
beforeEach(() => {
@@ -29,6 +29,83 @@ describe('ReactDOMSuspensePlaceholder', () => {
2929
document.body.removeChild(container);
3030
});
3131

32+
it('can ReactDOM.render() from useEffect', () => {
33+
let container2 = document.createElement('div');
34+
let container3 = document.createElement('div');
35+
36+
function Example1({n}) {
37+
React.useEffect(() => {
38+
ReactDOM.render(<Example2 n={n} />, container2);
39+
});
40+
return 1 * n;
41+
}
42+
43+
function Example2({n}) {
44+
React.useEffect(() => {
45+
ReactDOM.render(<Example3 n={n} />, container3);
46+
});
47+
return 2 * n;
48+
}
49+
50+
function Example3({n}) {
51+
return 3 * n;
52+
}
53+
54+
ReactDOM.render(<Example1 n={1} />, container);
55+
expect(container.textContent).toBe('1');
56+
expect(container2.textContent).toBe('');
57+
expect(container3.textContent).toBe('');
58+
jest.runAllTimers();
59+
expect(container.textContent).toBe('1');
60+
expect(container2.textContent).toBe('2');
61+
expect(container3.textContent).toBe('3');
62+
63+
ReactDOM.render(<Example1 n={2} />, container);
64+
expect(container.textContent).toBe('2');
65+
expect(container2.textContent).toBe('2'); // Not flushed yet
66+
expect(container3.textContent).toBe('3'); // Not flushed yet
67+
jest.runAllTimers();
68+
expect(container.textContent).toBe('2');
69+
expect(container2.textContent).toBe('4');
70+
expect(container3.textContent).toBe('6');
71+
});
72+
73+
it('can batch synchronous work inside effects with other work', () => {
74+
let otherContainer = document.createElement('div');
75+
76+
let calledA = false;
77+
function A() {
78+
calledA = true;
79+
return 'A';
80+
}
81+
82+
let calledB = false;
83+
function B() {
84+
calledB = true;
85+
return 'B';
86+
}
87+
88+
let _set;
89+
function Foo() {
90+
_set = React.useState(0)[1];
91+
React.useEffect(() => {
92+
ReactDOM.render(<A />, otherContainer);
93+
});
94+
return null;
95+
}
96+
97+
ReactDOM.render(<Foo />, container);
98+
ReactDOM.unstable_batchedUpdates(() => {
99+
_set(0); // Forces the effect to be flushed
100+
expect(otherContainer.textContent).toBe('');
101+
ReactDOM.render(<B />, otherContainer);
102+
expect(otherContainer.textContent).toBe('');
103+
});
104+
expect(otherContainer.textContent).toBe('B');
105+
expect(calledA).toBe(false); // It was in a batch
106+
expect(calledB).toBe(true);
107+
});
108+
32109
it('should not bail out when an update is scheduled from within an event handler', () => {
33110
const {createRef, useCallback, useState} = React;
34111

packages/react-reconciler/src/ReactFiberScheduler.js

+4
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,10 @@ function commitPassiveEffects(root: FiberRoot, firstEffect: Fiber): void {
575575
if (rootExpirationTime !== NoWork) {
576576
requestWork(root, rootExpirationTime);
577577
}
578+
// Flush any sync work that was scheduled by effects
579+
if (!isBatchingUpdates && !isRendering) {
580+
performSyncWork();
581+
}
578582
}
579583

580584
function isAlreadyFailedLegacyErrorBoundary(instance: mixed): boolean {

0 commit comments

Comments
 (0)