@samitouri / QOS-React / commits / 63651c49e0

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 }