Skip to content

Commit 53ce0c3

Browse files
authored
Allow flushSync to noop in life cycles but with a warning (facebook#18759)
1 parent f342a23 commit 53ce0c3

File tree

4 files changed

+39
-29
lines changed

4 files changed

+39
-29
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ describe('ReactDOMFiberAsync', () => {
127127
expect(ops).toEqual(['A', 'ABCD']);
128128
});
129129

130-
it('flushSync throws if already performing work', () => {
130+
it('flushSync logs an error if already performing work', () => {
131131
class Component extends React.Component {
132132
componentDidUpdate() {
133133
ReactDOM.flushSync(() => {});
@@ -140,7 +140,7 @@ describe('ReactDOMFiberAsync', () => {
140140
// Initial mount
141141
ReactDOM.render(<Component />, container);
142142
// Update
143-
expect(() => ReactDOM.render(<Component />, container)).toThrow(
143+
expect(() => ReactDOM.render(<Component />, container)).toErrorDev(
144144
'flushSync was called from inside a lifecycle method',
145145
);
146146
});

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1228,14 +1228,17 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
12281228
}
12291229

12301230
export function flushSync<A, R>(fn: A => R, a: A): R {
1231-
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
1232-
invariant(
1233-
false,
1234-
'flushSync was called from inside a lifecycle method. It cannot be ' +
1235-
'called when React is already rendering.',
1236-
);
1237-
}
12381231
const prevExecutionContext = executionContext;
1232+
if ((prevExecutionContext & (RenderContext | CommitContext)) !== NoContext) {
1233+
if (__DEV__) {
1234+
console.error(
1235+
'flushSync was called from inside a lifecycle method. React cannot ' +
1236+
'flush when React is already rendering. Consider moving this call to ' +
1237+
'a scheduler task or micro task.',
1238+
);
1239+
}
1240+
return fn(a);
1241+
}
12391242
executionContext |= BatchedContext;
12401243
try {
12411244
return runWithPriority(ImmediateSchedulerPriority, fn.bind(null, a));

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1150,14 +1150,17 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
11501150
}
11511151

11521152
export function flushSync<A, R>(fn: A => R, a: A): R {
1153-
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
1154-
invariant(
1155-
false,
1156-
'flushSync was called from inside a lifecycle method. It cannot be ' +
1157-
'called when React is already rendering.',
1158-
);
1159-
}
11601153
const prevExecutionContext = executionContext;
1154+
if ((prevExecutionContext & (RenderContext | CommitContext)) !== NoContext) {
1155+
if (__DEV__) {
1156+
console.error(
1157+
'flushSync was called from inside a lifecycle method. React cannot ' +
1158+
'flush when React is already rendering. Consider moving this call to ' +
1159+
'a scheduler task or micro task.',
1160+
);
1161+
}
1162+
return fn(a);
1163+
}
11611164
executionContext |= BatchedContext;
11621165
try {
11631166
return runWithPriority(ImmediatePriority, fn.bind(null, a));

packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1808,22 +1808,26 @@ describe('ReactHooksWithNoopRenderer', () => {
18081808
ReactNoop.flushSync(() => {
18091809
updateCount(props.count);
18101810
});
1811+
// This shouldn't flush synchronously.
1812+
expect(ReactNoop.getChildren()).not.toEqual([
1813+
span('Count: ' + props.count),
1814+
]);
18111815
}, [props.count]);
18121816
return <Text text={'Count: ' + count} />;
18131817
}
1814-
act(() => {
1815-
ReactNoop.render(<Counter count={0} />, () =>
1816-
Scheduler.unstable_yieldValue('Sync effect'),
1817-
);
1818-
expect(Scheduler).toFlushAndYieldThrough([
1819-
'Count: (empty)',
1820-
'Sync effect',
1821-
]);
1822-
expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]);
1823-
expect(() => {
1824-
ReactNoop.flushPassiveEffects();
1825-
}).toThrow('flushSync was called from inside a lifecycle method');
1826-
});
1818+
expect(() =>
1819+
act(() => {
1820+
ReactNoop.render(<Counter count={0} />, () =>
1821+
Scheduler.unstable_yieldValue('Sync effect'),
1822+
);
1823+
expect(Scheduler).toFlushAndYieldThrough([
1824+
'Count: (empty)',
1825+
'Sync effect',
1826+
]);
1827+
expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]);
1828+
}),
1829+
).toErrorDev('flushSync was called from inside a lifecycle method');
1830+
expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]);
18271831
});
18281832

18291833
it('unmounts previous effect', () => {

0 commit comments

Comments
 (0)