Allow flushSync to noop in life cycles but with a warning (#18759)
Sebastian Markbåge committed
Apr 27, 2020 at 20:07 UTC
53ce0c34524349501cdadf4fea187671aba6a38d
4 files changed
+39
-29
packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js
+2
-2
@@ -127,7 +127,7 @@ describe('ReactDOMFiberAsync', () => {
127
expect(ops).toEqual(['A', 'ABCD']);
128
});
129
130
- it('flushSync throws if already performing work', () => {
130
+ it('flushSync logs an error if already performing work', () => {
131
class Component extends React.Component {
132
componentDidUpdate() {
133
ReactDOM.flushSync(() => {});
@@ -140,7 +140,7 @@ describe('ReactDOMFiberAsync', () => {
140
// Initial mount
141
ReactDOM.render(<Component />, container);
142
// Update
143
- expect(() => ReactDOM.render(<Component />, container)).toThrow(
143
+ expect(() => ReactDOM.render(<Component />, container)).toErrorDev(
144
'flushSync was called from inside a lifecycle method',
145
);
146
});
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+10
-7
@@ -1228,14 +1228,17 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1228
}
1229
1230
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
- }
1231
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
+ }
1242
executionContext |= BatchedContext;
1243
try {
1244
return runWithPriority(ImmediateSchedulerPriority, fn.bind(null, a));
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+10
-7
@@ -1150,14 +1150,17 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1150
}
1151
1152
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
- }
1153
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
+ }
1164
executionContext |= BatchedContext;
1165
try {
1166
return runWithPriority(ImmediatePriority, fn.bind(null, a));
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+17
-13
@@ -1808,22 +1808,26 @@ describe('ReactHooksWithNoopRenderer', () => {
1808
ReactNoop.flushSync(() => {
1809
updateCount(props.count);
1810
});
1811
+ // This shouldn't flush synchronously.
1812
+ expect(ReactNoop.getChildren()).not.toEqual([
1813
+ span('Count: ' + props.count),
1814
+ ]);
1815
}, [props.count]);
1816
return <Text text={'Count: ' + count} />;
1817
}
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')]);
1831
});
1832
1833
it('unmounts previous effect', () => {