diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
index 8b5a924f02cdd..e16a1405b871e 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.new.js
@@ -1166,16 +1166,6 @@ export function unbatchedUpdates(fn: (a: A) => R, a: A): R {
export function flushSync(fn: A => R, a: A): R {
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;
const previousPriority = getCurrentUpdatePriority();
@@ -1192,7 +1182,17 @@ export function flushSync(fn: A => R, a: A): R {
// Flush the immediate callbacks that were scheduled during this batch.
// Note that this will happen even if batchedUpdates is higher up
// the stack.
- flushSyncCallbackQueue();
+ if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
+ flushSyncCallbackQueue();
+ } else {
+ 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.',
+ );
+ }
+ }
}
}
diff --git a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
index 3ee88a2feaf14..9446db83ca08e 100644
--- a/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+++ b/packages/react-reconciler/src/ReactFiberWorkLoop.old.js
@@ -1166,16 +1166,6 @@ export function unbatchedUpdates(fn: (a: A) => R, a: A): R {
export function flushSync(fn: A => R, a: A): R {
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;
const previousPriority = getCurrentUpdatePriority();
@@ -1192,7 +1182,17 @@ export function flushSync(fn: A => R, a: A): R {
// Flush the immediate callbacks that were scheduled during this batch.
// Note that this will happen even if batchedUpdates is higher up
// the stack.
- flushSyncCallbackQueue();
+ if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
+ flushSyncCallbackQueue();
+ } else {
+ 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.',
+ );
+ }
+ }
}
}
diff --git a/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js b/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js
new file mode 100644
index 0000000000000..3c96f5ee65914
--- /dev/null
+++ b/packages/react-reconciler/src/__tests__/ReactFlushSync-test.js
@@ -0,0 +1,57 @@
+let React;
+let ReactNoop;
+let Scheduler;
+let useState;
+let useEffect;
+
+describe('ReactFlushSync', () => {
+ beforeEach(() => {
+ jest.resetModules();
+
+ React = require('react');
+ ReactNoop = require('react-noop-renderer');
+ Scheduler = require('scheduler');
+ useState = React.useState;
+ useEffect = React.useEffect;
+ });
+
+ function Text({text}) {
+ Scheduler.unstable_yieldValue(text);
+ return text;
+ }
+
+ test('changes priority of updates in useEffect', async () => {
+ function App() {
+ const [syncState, setSyncState] = useState(0);
+ const [state, setState] = useState(0);
+ useEffect(() => {
+ if (syncState !== 1) {
+ setState(1);
+ ReactNoop.flushSync(() => setSyncState(1));
+ }
+ }, [syncState, state]);
+ return ;
+ }
+
+ const root = ReactNoop.createRoot();
+ await ReactNoop.act(async () => {
+ root.render();
+ // This will yield right before the passive effect fires
+ expect(Scheduler).toFlushUntilNextPaint(['0, 0']);
+
+ // The passive effect will schedule a sync update and a normal update.
+ // They should commit in two separate batches. First the sync one.
+ expect(() =>
+ expect(Scheduler).toFlushUntilNextPaint(['1, 0']),
+ ).toErrorDev('flushSync was called from inside a lifecycle method');
+
+ // The remaining update is not sync
+ ReactNoop.flushSync();
+ expect(Scheduler).toHaveYielded([]);
+
+ // Now flush it.
+ expect(Scheduler).toFlushUntilNextPaint(['1, 1']);
+ });
+ expect(root).toMatchRenderedOutput('1, 1');
+ });
+});