Skip to content

Commit d08b6cf

Browse files
committed
Revert "Revert "act: Move didScheduleLegacyUpdate to ensureRootIsScheduled (#26552)""
This reverts commit 7b6e17c.
1 parent 51ca80b commit d08b6cf

File tree

4 files changed

+80
-15
lines changed

4 files changed

+80
-15
lines changed

packages/react-reconciler/src/ReactFiberRootScheduler.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,15 @@ export function ensureRootIsScheduled(root: FiberRoot): void {
120120
// unblock additional features we have planned.
121121
scheduleTaskForRootDuringMicrotask(root, now());
122122
}
123+
124+
if (
125+
__DEV__ &&
126+
ReactCurrentActQueue.isBatchingLegacy &&
127+
root.tag === LegacyRoot
128+
) {
129+
// Special `act` case: Record whenever a legacy update is scheduled.
130+
ReactCurrentActQueue.didScheduleLegacyUpdate = true;
131+
}
123132
}
124133

125134
export function flushSyncWorkOnAllRoots() {

packages/react-reconciler/src/ReactFiberWorkLoop.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -823,7 +823,6 @@ export function scheduleUpdateOnFiber(
823823
) {
824824
if (__DEV__ && ReactCurrentActQueue.isBatchingLegacy) {
825825
// Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
826-
ReactCurrentActQueue.didScheduleLegacyUpdate = true;
827826
} else {
828827
// Flush the synchronous work now, unless we're already working or inside
829828
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of

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

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@ let Suspense;
1717
let DiscreteEventPriority;
1818
let startTransition;
1919
let waitForMicrotasks;
20+
let Scheduler;
21+
let assertLog;
2022

2123
describe('isomorphic act()', () => {
2224
beforeEach(() => {
2325
React = require('react');
26+
Scheduler = require('scheduler');
2427

2528
ReactNoop = require('react-noop-renderer');
2629
DiscreteEventPriority =
@@ -31,6 +34,7 @@ describe('isomorphic act()', () => {
3134
startTransition = React.startTransition;
3235

3336
waitForMicrotasks = require('internal-test-utils').waitForMicrotasks;
37+
assertLog = require('internal-test-utils').assertLog;
3438
});
3539

3640
beforeEach(() => {
@@ -41,6 +45,11 @@ describe('isomorphic act()', () => {
4145
jest.restoreAllMocks();
4246
});
4347

48+
function Text({text}) {
49+
Scheduler.log(text);
50+
return text;
51+
}
52+
4453
// @gate __DEV__
4554
test('bypasses queueMicrotask', async () => {
4655
const root = ReactNoop.createRoot();
@@ -132,19 +141,67 @@ describe('isomorphic act()', () => {
132141
const root = ReactNoop.createLegacyRoot();
133142

134143
await act(async () => {
135-
// These updates are batched. This replicates the behavior of the original
136-
// `act` implementation, for compatibility.
137-
root.render('A');
138-
root.render('B');
139-
// Nothing has rendered yet.
140-
expect(root).toMatchRenderedOutput(null);
144+
queueMicrotask(() => {
145+
Scheduler.log('Current tree in microtask: ' + root.getChildrenAsJSX());
146+
root.render(<Text text="C" />);
147+
});
148+
root.render(<Text text="A" />);
149+
root.render(<Text text="B" />);
150+
141151
await null;
142-
// Updates are flushed after the first await.
143-
expect(root).toMatchRenderedOutput('B');
152+
assertLog([
153+
// A and B should render in a single batch _before_ the microtask queue
154+
// has run. This replicates the behavior of the original `act`
155+
// implementation, for compatibility.
156+
'B',
157+
'Current tree in microtask: B',
158+
159+
// C isn't scheduled until a microtask, so it's rendered separately.
160+
'C',
161+
]);
162+
163+
// Subsequent updates should also render in separate batches.
164+
root.render(<Text text="D" />);
165+
root.render(<Text text="E" />);
166+
assertLog(['D', 'E']);
167+
});
168+
});
144169

145-
// Subsequent updates in the same scope aren't batched.
146-
root.render('C');
147-
expect(root).toMatchRenderedOutput('C');
170+
// @gate __DEV__
171+
test('in legacy mode, in an async scope, updates are batched until the first `await` (regression test: batchedUpdates)', async () => {
172+
const root = ReactNoop.createLegacyRoot();
173+
174+
await act(async () => {
175+
queueMicrotask(() => {
176+
Scheduler.log('Current tree in microtask: ' + root.getChildrenAsJSX());
177+
root.render(<Text text="C" />);
178+
});
179+
180+
// This is a regression test. The presence of `batchedUpdates` would cause
181+
// these updates to not flush until a microtask. The correct behavior is
182+
// that they flush before the microtask queue, regardless of whether
183+
// they are wrapped with `batchedUpdates`.
184+
ReactNoop.batchedUpdates(() => {
185+
root.render(<Text text="A" />);
186+
root.render(<Text text="B" />);
187+
});
188+
189+
await null;
190+
assertLog([
191+
// A and B should render in a single batch _before_ the microtask queue
192+
// has run. This replicates the behavior of the original `act`
193+
// implementation, for compatibility.
194+
'B',
195+
'Current tree in microtask: B',
196+
197+
// C isn't scheduled until a microtask, so it's rendered separately.
198+
'C',
199+
]);
200+
201+
// Subsequent updates should also render in separate batches.
202+
root.render(<Text text="D" />);
203+
root.render(<Text text="E" />);
204+
assertLog(['D', 'E']);
148205
});
149206
});
150207

scripts/jest/matchers/reactTestMatchers.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,21 @@ function captureAssertion(fn) {
2020
return {pass: true};
2121
}
2222

23-
function assertYieldsWereCleared(Scheduler) {
23+
function assertYieldsWereCleared(Scheduler, caller) {
2424
const actualYields = Scheduler.unstable_clearLog();
2525
if (actualYields.length !== 0) {
2626
const error = Error(
2727
'The event log is not empty. Call assertLog(...) first.'
2828
);
29-
Error.captureStackTrace(error, assertYieldsWereCleared);
29+
Error.captureStackTrace(error, caller);
3030
throw error;
3131
}
3232
}
3333

3434
function toMatchRenderedOutput(ReactNoop, expectedJSX) {
3535
if (typeof ReactNoop.getChildrenAsJSX === 'function') {
3636
const Scheduler = ReactNoop._Scheduler;
37-
assertYieldsWereCleared(Scheduler);
37+
assertYieldsWereCleared(Scheduler, toMatchRenderedOutput);
3838
return captureAssertion(() => {
3939
expect(ReactNoop.getChildrenAsJSX()).toEqual(expectedJSX);
4040
});

0 commit comments

Comments
 (0)