Skip to content

Commit 760abec

Browse files
acdlitesebmarkbage
authored andcommitted
Support nesting of startTransition and flushSync
1 parent 0853aab commit 760abec

File tree

3 files changed

+51
-0
lines changed

3 files changed

+51
-0
lines changed

packages/react-reconciler/src/ReactFiberWorkLoop.new.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ const ceil = Math.ceil;
240240
const {
241241
ReactCurrentDispatcher,
242242
ReactCurrentOwner,
243+
ReactCurrentBatchConfig,
243244
IsSomeRendererActing,
244245
} = ReactSharedInternals;
245246

@@ -1151,8 +1152,10 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
11511152
const prevExecutionContext = executionContext;
11521153
executionContext |= BatchedContext;
11531154

1155+
const prevTransition = ReactCurrentBatchConfig.transition;
11541156
const previousPriority = getCurrentUpdatePriority();
11551157
try {
1158+
ReactCurrentBatchConfig.transition = 0;
11561159
setCurrentUpdatePriority(DiscreteEventPriority);
11571160
if (fn) {
11581161
return fn(a);
@@ -1161,6 +1164,7 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
11611164
}
11621165
} finally {
11631166
setCurrentUpdatePriority(previousPriority);
1167+
ReactCurrentBatchConfig.transition = prevTransition;
11641168
executionContext = prevExecutionContext;
11651169
// Flush the immediate callbacks that were scheduled during this batch.
11661170
// Note that this will happen even if batchedUpdates is higher up

packages/react-reconciler/src/ReactFiberWorkLoop.old.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ const ceil = Math.ceil;
240240
const {
241241
ReactCurrentDispatcher,
242242
ReactCurrentOwner,
243+
ReactCurrentBatchConfig,
243244
IsSomeRendererActing,
244245
} = ReactSharedInternals;
245246

@@ -1151,8 +1152,10 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
11511152
const prevExecutionContext = executionContext;
11521153
executionContext |= BatchedContext;
11531154

1155+
const prevTransition = ReactCurrentBatchConfig.transition;
11541156
const previousPriority = getCurrentUpdatePriority();
11551157
try {
1158+
ReactCurrentBatchConfig.transition = 0;
11561159
setCurrentUpdatePriority(DiscreteEventPriority);
11571160
if (fn) {
11581161
return fn(a);
@@ -1161,6 +1164,7 @@ export function flushSync<A, R>(fn: A => R, a: A): R {
11611164
}
11621165
} finally {
11631166
setCurrentUpdatePriority(previousPriority);
1167+
ReactCurrentBatchConfig.transition = prevTransition;
11641168
executionContext = prevExecutionContext;
11651169
// Flush the immediate callbacks that were scheduled during this batch.
11661170
// Note that this will happen even if batchedUpdates is higher up

packages/react-reconciler/src/__tests__/ReactFlushSync-test.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ let ReactNoop;
33
let Scheduler;
44
let useState;
55
let useEffect;
6+
let startTransition;
67

78
describe('ReactFlushSync', () => {
89
beforeEach(() => {
@@ -13,6 +14,7 @@ describe('ReactFlushSync', () => {
1314
Scheduler = require('scheduler');
1415
useState = React.useState;
1516
useEffect = React.useEffect;
17+
startTransition = React.unstable_startTransition;
1618
});
1719

1820
function Text({text}) {
@@ -54,4 +56,45 @@ describe('ReactFlushSync', () => {
5456
});
5557
expect(root).toMatchRenderedOutput('1, 1');
5658
});
59+
60+
// @gate experimental
61+
test('nested with startTransition', async () => {
62+
let setSyncState;
63+
let setState;
64+
function App() {
65+
const [syncState, _setSyncState] = useState(0);
66+
const [state, _setState] = useState(0);
67+
setSyncState = _setSyncState;
68+
setState = _setState;
69+
return <Text text={`${syncState}, ${state}`} />;
70+
}
71+
72+
const root = ReactNoop.createRoot();
73+
await ReactNoop.act(async () => {
74+
root.render(<App />);
75+
});
76+
expect(Scheduler).toHaveYielded(['0, 0']);
77+
expect(root).toMatchRenderedOutput('0, 0');
78+
79+
await ReactNoop.act(async () => {
80+
ReactNoop.flushSync(() => {
81+
startTransition(() => {
82+
// This should be async even though flushSync is on the stack, because
83+
// startTransition is closer.
84+
setState(1);
85+
ReactNoop.flushSync(() => {
86+
// This should be async even though startTransition is on the stack,
87+
// because flushSync is closer.
88+
setSyncState(1);
89+
});
90+
});
91+
});
92+
// Only the sync update should have flushed
93+
expect(Scheduler).toHaveYielded(['1, 0']);
94+
expect(root).toMatchRenderedOutput('1, 0');
95+
});
96+
// Now the async update has flushed, too.
97+
expect(Scheduler).toHaveYielded(['1, 1']);
98+
expect(root).toMatchRenderedOutput('1, 1');
99+
});
57100
});

0 commit comments

Comments
 (0)