@samitouri / QOS-React-2 / commits / e7e0a90bd8

Revert "Fix: flushSync changes priority inside effect (#21122)"

This reverts commit 0e3c7e1d62efb6238b69e5295d45b9bd2dcf9181. When called from inside an effect, flushSync cannot synchronously flush its updates because React is already working. So we fire a warning. However, we should still change the priority of the updates to sync so that they flush at the end of the current task. This only affects useEffect because updates inside useLayoutEffect (and the rest of the commit phase, like ref callbacks) are already sync.

Andrew Clark committed Apr 23, 2021 at 11:48 UTC e7e0a90bd8558af16bb6aa278097536d9b69643c
3 files changed +22 -86
packages/react-reconciler/src/ReactFiberWorkLoop.new.js
+11 -11
@@ -1142,6 +1142,16 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1142
1143 export function flushSync<A, R>(fn: A => R, a: A): R {
1144 const prevExecutionContext = executionContext;
1145 + if ((prevExecutionContext & (RenderContext | CommitContext)) !== NoContext) {
1146 + if (__DEV__) {
1147 + console.error(
1148 + 'flushSync was called from inside a lifecycle method. React cannot ' +
1149 + 'flush when React is already rendering. Consider moving this call to ' +
1150 + 'a scheduler task or micro task.',
1151 + );
1152 + }
1153 + return fn(a);
1154 + }
1155 executionContext |= BatchedContext;
1156
1157 const previousPriority = getCurrentUpdatePriority();
@@ -1158,17 +1168,7 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
1168 // Flush the immediate callbacks that were scheduled during this batch.
1169 // Note that this will happen even if batchedUpdates is higher up
1170 // the stack.
1161 - if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1162 - flushSyncCallbacks();
1163 - } else {
1164 - if (__DEV__) {
1165 - console.error(
1166 - 'flushSync was called from inside a lifecycle method. React cannot ' +
1167 - 'flush when React is already rendering. Consider moving this call to ' +
1168 - 'a scheduler task or micro task.',
1169 - );
1170 - }
1171 - }
1171 + flushSyncCallbacks();
1172 }
1173 }
1174
packages/react-reconciler/src/ReactFiberWorkLoop.old.js
+11 -11
@@ -1142,6 +1142,16 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
1142
1143 export function flushSync<A, R>(fn: A => R, a: A): R {
1144 const prevExecutionContext = executionContext;
1145 + if ((prevExecutionContext & (RenderContext | CommitContext)) !== NoContext) {
1146 + if (__DEV__) {
1147 + console.error(
1148 + 'flushSync was called from inside a lifecycle method. React cannot ' +
1149 + 'flush when React is already rendering. Consider moving this call to ' +
1150 + 'a scheduler task or micro task.',
1151 + );
1152 + }
1153 + return fn(a);
1154 + }
1155 executionContext |= BatchedContext;
1156
1157 const previousPriority = getCurrentUpdatePriority();
@@ -1158,17 +1168,7 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
1168 // Flush the immediate callbacks that were scheduled during this batch.
1169 // Note that this will happen even if batchedUpdates is higher up
1170 // the stack.
1161 - if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
1162 - flushSyncCallbacks();
1163 - } else {
1164 - if (__DEV__) {
1165 - console.error(
1166 - 'flushSync was called from inside a lifecycle method. React cannot ' +
1167 - 'flush when React is already rendering. Consider moving this call to ' +
1168 - 'a scheduler task or micro task.',
1169 - );
1170 - }
1171 - }
1171 + flushSyncCallbacks();
1172 }
1173 }
1174
packages/react-reconciler/src/__tests__/ReactFlushSync-test.js deleted
-64
@@ -1,64 +0,0 @@
1 -let React;
2 -let ReactNoop;
3 -let Scheduler;
4 -let useState;
5 -let useEffect;
6 -
7 -describe('ReactFlushSync', () => {
8 - beforeEach(() => {
9 - jest.resetModules();
10 -
11 - React = require('react');
12 - ReactNoop = require('react-noop-renderer');
13 - Scheduler = require('scheduler');
14 - useState = React.useState;
15 - useEffect = React.useEffect;
16 - });
17 -
18 - function Text({text}) {
19 - Scheduler.unstable_yieldValue(text);
20 - return text;
21 - }
22 -
23 - // @gate experimental || !enableSyncDefaultUpdates
24 - test('changes priority of updates in useEffect', async () => {
25 - function App() {
26 - const [syncState, setSyncState] = useState(0);
27 - const [state, setState] = useState(0);
28 - useEffect(() => {
29 - if (syncState !== 1) {
30 - setState(1);
31 - ReactNoop.flushSync(() => setSyncState(1));
32 - }
33 - }, [syncState, state]);
34 - return <Text text={`${syncState}, ${state}`} />;
35 - }
36 -
37 - const root = ReactNoop.createRoot();
38 - await ReactNoop.act(async () => {
39 - if (gate(flags => flags.enableSyncDefaultUpdates)) {
40 - React.unstable_startTransition(() => {
41 - root.render(<App />);
42 - });
43 - } else {
44 - root.render(<App />);
45 - }
46 - // This will yield right before the passive effect fires
47 - expect(Scheduler).toFlushUntilNextPaint(['0, 0']);
48 -
49 - // The passive effect will schedule a sync update and a normal update.
50 - // They should commit in two separate batches. First the sync one.
51 - expect(() => {
52 - expect(Scheduler).toFlushUntilNextPaint(['1, 0']);
53 - }).toErrorDev('flushSync was called from inside a lifecycle method');
54 -
55 - // The remaining update is not sync
56 - ReactNoop.flushSync();
57 - expect(Scheduler).toHaveYielded([]);
58 -
59 - // Now flush it.
60 - expect(Scheduler).toFlushUntilNextPaint(['1, 1']);
61 - });
62 - expect(root).toMatchRenderedOutput('1, 1');
63 - });
64 -});