diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js index 889b26387d8f9..77dd7e53355da 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js @@ -127,7 +127,7 @@ describe('ReactDOMFiberAsync', () => { expect(ops).toEqual(['A', 'ABCD']); }); - it('flushSync throws if already performing work', () => { + it('flushSync logs an error if already performing work', () => { class Component extends React.Component { componentDidUpdate() { ReactDOM.flushSync(() => {}); @@ -140,7 +140,7 @@ describe('ReactDOMFiberAsync', () => { // Initial mount ReactDOM.render(, container); // Update - expect(() => ReactDOM.render(, container)).toThrow( + expect(() => ReactDOM.render(, container)).toErrorDev( 'flushSync was called from inside a lifecycle method', ); }); diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js index c4841ac63fa9c..2799e38237d58 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js @@ -1108,14 +1108,17 @@ export function unbatchedUpdates(fn: (a: A) => R, a: A): R { } export function flushSync(fn: A => R, a: A): R { - if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { - invariant( - false, - 'flushSync was called from inside a lifecycle method. It cannot be ' + - 'called when React is already rendering.', - ); - } const prevExecutionContext = executionContext; + if ((prevExecutionContext & (RenderContext | CommitContext)) !== NoContext) { + if (__DEV__) { + console.error( + 'flushSync was called from inside a lifecycle method. React cannot ' + + 'flush when React is already rendering. Consider moving this call to ' + + 'a scheduler task or micro task.', + ); + } + return fn(a); + } executionContext |= BatchedContext; try { return runWithPriority(ImmediatePriority, fn.bind(null, a)); diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js index a771b96568d16..6ce1eca55bd96 100644 --- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js +++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js @@ -1150,14 +1150,17 @@ export function unbatchedUpdates(fn: (a: A) => R, a: A): R { } export function flushSync(fn: A => R, a: A): R { - if ((executionContext & (RenderContext | CommitContext)) !== NoContext) { - invariant( - false, - 'flushSync was called from inside a lifecycle method. It cannot be ' + - 'called when React is already rendering.', - ); - } const prevExecutionContext = executionContext; + if ((prevExecutionContext & (RenderContext | CommitContext)) !== NoContext) { + if (__DEV__) { + console.error( + 'flushSync was called from inside a lifecycle method. React cannot ' + + 'flush when React is already rendering. Consider moving this call to ' + + 'a scheduler task or micro task.', + ); + } + return fn(a); + } executionContext |= BatchedContext; try { return runWithPriority(ImmediatePriority, fn.bind(null, a)); diff --git a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js index 424dc60946ac5..13ea6cf0748ee 100644 --- a/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js +++ b/packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js @@ -1808,22 +1808,26 @@ describe('ReactHooksWithNoopRenderer', () => { ReactNoop.flushSync(() => { updateCount(props.count); }); + // This shouldn't flush synchronously. + expect(ReactNoop.getChildren()).not.toEqual([ + span('Count: ' + props.count), + ]); }, [props.count]); return ; } - act(() => { - ReactNoop.render(, () => - Scheduler.unstable_yieldValue('Sync effect'), - ); - expect(Scheduler).toFlushAndYieldThrough([ - 'Count: (empty)', - 'Sync effect', - ]); - expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); - expect(() => { - ReactNoop.flushPassiveEffects(); - }).toThrow('flushSync was called from inside a lifecycle method'); - }); + expect(() => + act(() => { + ReactNoop.render(, () => + Scheduler.unstable_yieldValue('Sync effect'), + ); + expect(Scheduler).toFlushAndYieldThrough([ + 'Count: (empty)', + 'Sync effect', + ]); + expect(ReactNoop.getChildren()).toEqual([span('Count: (empty)')]); + }), + ).toErrorDev('flushSync was called from inside a lifecycle method'); + expect(ReactNoop.getChildren()).toEqual([span('Count: 0')]); }); it('unmounts previous effect', () => {