Noop unstable_batchedUpdates (#28120)
## Overview `unstable_batchedUpdates` is effectively a no-op outside of legacy mode, this PR makes it an actual no-op outside legacy mode.
Ricky committed
Mar 28, 2024 at 14:15 UTC
63651c49e068a04cdc6ee1e2fa9c6125167987d2
6 files changed
+49
-29
packages/react-dom/index.classic.fb.js
+6
-2
@@ -23,7 +23,6 @@ export {
23
findDOMNode,
24
flushSync,
25
unmountComponentAtNode,
26
- unstable_batchedUpdates,
26
unstable_createEventHandle,
27
unstable_renderSubtreeIntoContainer,
28
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
@@ -38,6 +37,11 @@ export {
37
version,
38
} from './src/client/ReactDOM';
39
41
-export {createRoot, hydrateRoot, render} from './src/client/ReactDOMRootFB';
40
+export {
41
+ createRoot,
42
+ hydrateRoot,
43
+ render,
44
+ unstable_batchedUpdates,
45
+} from './src/client/ReactDOMRootFB';
46
47
export {Internals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED};
packages/react-dom/src/__tests__/ReactLegacyMount-test.js
+1
-1
@@ -248,7 +248,7 @@ describe('ReactMount', () => {
248
expect(calls).toBe(5);
249
});
250
251
- // @gate !disableLegacyMode
251
+ // @gate !disableLegacyMode && classic
252
it('initial mount of legacy root is sync inside batchedUpdates, as if it were wrapped in flushSync', () => {
253
const container1 = document.createElement('div');
254
const container2 = document.createElement('div');
packages/react-dom/src/__tests__/ReactLegacyUpdates-test.js
+9
-9
@@ -32,7 +32,7 @@ describe('ReactLegacyUpdates', () => {
32
assertLog = InternalTestUtils.assertLog;
33
});
34
35
- // @gate !disableLegacyMode
35
+ // @gate !disableLegacyMode && classic
36
it('should batch state when updating state twice', () => {
37
let updateCount = 0;
38
@@ -63,7 +63,7 @@ describe('ReactLegacyUpdates', () => {
63
expect(updateCount).toBe(1);
64
});
65
66
- // @gate !disableLegacyMode
66
+ // @gate !disableLegacyMode && classic
67
it('should batch state when updating two different state keys', () => {
68
let updateCount = 0;
69
@@ -97,7 +97,7 @@ describe('ReactLegacyUpdates', () => {
97
expect(updateCount).toBe(1);
98
});
99
100
- // @gate !disableLegacyMode
100
+ // @gate !disableLegacyMode && classic
101
it('should batch state and props together', () => {
102
let updateCount = 0;
103
@@ -131,7 +131,7 @@ describe('ReactLegacyUpdates', () => {
131
expect(updateCount).toBe(1);
132
});
133
134
- // @gate !disableLegacyMode
134
+ // @gate !disableLegacyMode && classic
135
it('should batch parent/child state updates together', () => {
136
let parentUpdateCount = 0;
137
@@ -187,7 +187,7 @@ describe('ReactLegacyUpdates', () => {
187
expect(childUpdateCount).toBe(1);
188
});
189
190
- // @gate !disableLegacyMode
190
+ // @gate !disableLegacyMode && classic
191
it('should batch child/parent state updates together', () => {
192
let parentUpdateCount = 0;
193
@@ -245,7 +245,7 @@ describe('ReactLegacyUpdates', () => {
245
expect(childUpdateCount).toBe(1);
246
});
247
248
- // @gate !disableLegacyMode
248
+ // @gate !disableLegacyMode && classic
249
it('should support chained state updates', () => {
250
let updateCount = 0;
251
@@ -286,7 +286,7 @@ describe('ReactLegacyUpdates', () => {
286
expect(updateCount).toBe(2);
287
});
288
289
- // @gate !disableLegacyMode
289
+ // @gate !disableLegacyMode && classic
290
it('should batch forceUpdate together', () => {
291
let shouldUpdateCount = 0;
292
let updateCount = 0;
@@ -548,7 +548,7 @@ describe('ReactLegacyUpdates', () => {
548
);
549
});
550
551
- // @gate !disableLegacyMode
551
+ // @gate !disableLegacyMode && classic
552
it('should queue mount-ready handlers across different roots', () => {
553
// We'll define two components A and B, then update both of them. When A's
554
// componentDidUpdate handlers is called, B's DOM should already have been
@@ -849,7 +849,7 @@ describe('ReactLegacyUpdates', () => {
849
expect(callbackCount).toBe(1);
850
});
851
852
- // @gate !disableLegacyMode
852
+ // @gate !disableLegacyMode && classic
853
it('does not call render after a component as been deleted', () => {
854
let renderCount = 0;
855
let componentB = null;
packages/react-dom/src/client/ReactDOM.js
+9
-3
@@ -32,7 +32,6 @@ import {
32
import {createEventHandle} from 'react-dom-bindings/src/client/ReactDOMEventHandle';
33
34
import {
35
- batchedUpdates,
35
flushSync as flushSyncWithoutWarningIfAlreadyRendering,
36
isAlreadyRendering,
37
injectIntoDevTools,
@@ -167,9 +166,16 @@ function flushSync<R>(fn: (() => R) | void): R | void {
166
// Expose findDOMNode on internals
167
Internals.findDOMNode = findDOMNode;
168
169
+function unstable_batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
170
+ // batchedUpdates was a legacy mode feature that is a no-op outside of
171
+ // legacy mode. In 19, we made it an actual no-op, but we're keeping it
172
+ // for now since there may be libraries that still include it.
173
+ return fn(a);
174
+}
175
+
176
export {
177
createPortal,
172
- batchedUpdates as unstable_batchedUpdates,
178
+ unstable_batchedUpdates,
179
flushSync,
180
ReactVersion as version,
181
// Disabled behind disableLegacyReactDOMAPIs
@@ -196,7 +202,7 @@ Internals.Events = [
202
getFiberCurrentPropsFromNode,
203
enqueueStateRestore,
204
restoreStateIfNeeded,
199
- batchedUpdates,
205
+ unstable_batchedUpdates,
206
];
207
208
const foundDevTools = injectIntoDevTools({
packages/react-dom/src/client/ReactDOMRootFB.js
+3
@@ -42,6 +42,7 @@ import {
42
} from 'react-dom-bindings/src/client/HTMLNodeType';
43
44
import {
45
+ batchedUpdates,
46
createContainer,
47
createHydrationContainer,
48
findHostInstanceWithNoPortals,
@@ -416,3 +417,5 @@ export function unstable_renderSubtreeIntoContainer(
417
callback,
418
);
419
}
420
+
421
+export {batchedUpdates as unstable_batchedUpdates};
packages/react-reconciler/src/ReactFiberWorkLoop.js
+21
-14
@@ -39,6 +39,7 @@ import {
39
disableLegacyContext,
40
alwaysThrottleRetries,
41
enableInfiniteRenderLoopDetection,
42
+ disableLegacyMode,
43
} from 'shared/ReactFeatureFlags';
44
import ReactSharedInternals from 'shared/ReactSharedInternals';
45
import is from 'shared/objectIs';
@@ -1456,21 +1457,27 @@ export function deferredUpdates<A>(fn: () => A): A {
1457
}
1458
1459
export function batchedUpdates<A, R>(fn: A => R, a: A): R {
1459
- const prevExecutionContext = executionContext;
1460
- executionContext |= BatchedContext;
1461
- try {
1460
+ if (disableLegacyMode) {
1461
+ // batchedUpdates is a no-op now, but there's still some internal react-dom
1462
+ // code calling it, that we can't remove until we remove legacy mode.
1463
return fn(a);
1463
- } finally {
1464
- executionContext = prevExecutionContext;
1465
- // If there were legacy sync updates, flush them at the end of the outer
1466
- // most batchedUpdates-like method.
1467
- if (
1468
- executionContext === NoContext &&
1469
- // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
1470
- !(__DEV__ && ReactCurrentActQueue.isBatchingLegacy)
1471
- ) {
1472
- resetRenderTimer();
1473
- flushSyncWorkOnLegacyRootsOnly();
1464
+ } else {
1465
+ const prevExecutionContext = executionContext;
1466
+ executionContext |= BatchedContext;
1467
+ try {
1468
+ return fn(a);
1469
+ } finally {
1470
+ executionContext = prevExecutionContext;
1471
+ // If there were legacy sync updates, flush them at the end of the outer
1472
+ // most batchedUpdates-like method.
1473
+ if (
1474
+ executionContext === NoContext &&
1475
+ // Treat `act` as if it's inside `batchedUpdates`, even in legacy mode.
1476
+ !(__DEV__ && ReactCurrentActQueue.isBatchingLegacy)
1477
+ ) {
1478
+ resetRenderTimer();
1479
+ flushSyncWorkOnLegacyRootsOnly();
1480
+ }
1481
}
1482
}
1483
}