mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Re-land "Fix: flushSync changes priority inside effect (#21122)"
This re-lands commit 0e3c7e1d62efb6238b69e5295d45b9bd2dcf9181.
This commit is contained in:
@@ -1142,16 +1142,6 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
|
||||
|
||||
export function flushSync<A, R>(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();
|
||||
@@ -1168,7 +1158,17 @@ export function flushSync<A, R>(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.
|
||||
flushSyncCallbacks();
|
||||
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
|
||||
flushSyncCallbacks();
|
||||
} 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.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1142,16 +1142,6 @@ export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
|
||||
|
||||
export function flushSync<A, R>(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();
|
||||
@@ -1168,7 +1158,17 @@ export function flushSync<A, R>(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.
|
||||
flushSyncCallbacks();
|
||||
if ((executionContext & (RenderContext | CommitContext)) === NoContext) {
|
||||
flushSyncCallbacks();
|
||||
} 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.',
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
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;
|
||||
}
|
||||
|
||||
// @gate experimental || !enableSyncDefaultUpdates
|
||||
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 <Text text={`${syncState}, ${state}`} />;
|
||||
}
|
||||
|
||||
const root = ReactNoop.createRoot();
|
||||
await ReactNoop.act(async () => {
|
||||
if (gate(flags => flags.enableSyncDefaultUpdates)) {
|
||||
React.unstable_startTransition(() => {
|
||||
root.render(<App />);
|
||||
});
|
||||
} else {
|
||||
root.render(<App />);
|
||||
}
|
||||
// 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');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user