Skip to content

Commit 5b37221

Browse files
committed
Moves the legacy implementation of flushSync to the fb entrypoint
1 parent 0ef7071 commit 5b37221

File tree

11 files changed

+257
-46
lines changed

11 files changed

+257
-46
lines changed

packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1949,7 +1949,7 @@ function flushSyncWork() {
19491949
const wasRendering = flushSyncWorkOnAllRoots();
19501950
// Since multiple dispatchers can flush sync work during a single flushSync call
19511951
// we need to return true if any of them were rendering.
1952-
return wasRendering || previousWasRendering;
1952+
return previousWasRendering || wasRendering;
19531953
}
19541954

19551955
// We expect this to get inlined. It is a function mostly to communicate the special nature of

packages/react-dom-bindings/src/events/ReactDOMUpdateBatching.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
import {
1414
batchedUpdates as batchedUpdatesImpl,
1515
discreteUpdates as discreteUpdatesImpl,
16-
flushSyncWork,
16+
flushSyncFromReconciler,
1717
} from 'react-reconciler/src/ReactFiberReconciler';
1818

1919
// Used as a way to call batchedUpdates when we don't have a reference to
@@ -36,7 +36,9 @@ function finishEventHandler() {
3636
// bails out of the update without touching the DOM.
3737
// TODO: Restore state in the microtask, after the discrete updates flush,
3838
// instead of early flushing them here.
39-
flushSyncWork();
39+
// @TODO Should move to flushSyncWork once legacy mode is removed but since this flushSync
40+
// flushes passive effects we can't do this yet.
41+
flushSyncFromReconciler();
4042
restoreStateIfNeeded();
4143
}
4244
}

packages/react-dom/index.classic.fb.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ export {
3232
preinit,
3333
preinitModule,
3434
version,
35-
} from './src/client/ReactDOM';
35+
} from './src/client/ReactDOMFB';
3636

3737
export {
3838
createRoot,

packages/react-dom/index.experimental.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export {
1212
createPortal,
1313
createRoot,
1414
hydrateRoot,
15+
flushSync,
1516
unstable_batchedUpdates,
1617
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
1718
useFormStatus,
@@ -24,5 +25,3 @@ export {
2425
preinitModule,
2526
version,
2627
} from './src/client/ReactDOM';
27-
28-
export {flushSync} from './src/shared/ReactDOMFlushSync';

packages/react-dom/src/ReactDOMSharedInternals.js

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,9 @@ type InternalsType = {
2323
};
2424

2525
function noop() {}
26-
function falseNoop() {
27-
return false;
28-
}
2926

3027
const DefaultDispatcher: HostDispatcher = {
31-
flushSyncWork: falseNoop,
28+
flushSyncWork: noop,
3229
prefetchDNS: noop,
3330
preconnect: noop,
3431
preload: noop,

packages/react-dom/src/client/ReactDOM.js

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,9 @@ import {
2020
isValidContainer,
2121
} from './ReactDOMRoot';
2222
import {createEventHandle} from 'react-dom-bindings/src/client/ReactDOMEventHandle';
23+
import {flushSync} from '../shared/ReactDOMFlushSync';
2324

2425
import {
25-
flushSyncFromReconciler as flushSyncWithoutWarningIfAlreadyRendering,
26-
isAlreadyRendering,
2726
injectIntoDevTools,
2827
findHostInstance,
2928
} from 'react-reconciler/src/ReactFiberReconciler';
@@ -121,25 +120,6 @@ function hydrateRoot(
121120
return hydrateRootImpl(container, initialChildren, options);
122121
}
123122

124-
// Overload the definition to the two valid signatures.
125-
// Warning, this opts-out of checking the function body.
126-
declare function flushSyncFromReconciler<R>(fn: () => R): R;
127-
// eslint-disable-next-line no-redeclare
128-
declare function flushSyncFromReconciler(): void;
129-
// eslint-disable-next-line no-redeclare
130-
function flushSyncFromReconciler<R>(fn: (() => R) | void): R | void {
131-
if (__DEV__) {
132-
if (isAlreadyRendering()) {
133-
console.error(
134-
'flushSync was called from inside a lifecycle method. React cannot ' +
135-
'flush when React is already rendering. Consider moving this call to ' +
136-
'a scheduler task or micro task.',
137-
);
138-
}
139-
}
140-
return flushSyncWithoutWarningIfAlreadyRendering(fn);
141-
}
142-
143123
function findDOMNode(
144124
componentOrElement: React$Component<any, any>,
145125
): null | Element | Text {
@@ -159,7 +139,7 @@ function unstable_batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
159139
export {
160140
createPortal,
161141
unstable_batchedUpdates,
162-
flushSyncFromReconciler as flushSync,
142+
flushSync,
163143
ReactVersion as version,
164144
// exposeConcurrentModeAPIs
165145
createRoot,
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow
8+
*/
9+
10+
import type {ReactNodeList} from 'shared/ReactTypes';
11+
import type {
12+
RootType,
13+
HydrateRootOptions,
14+
CreateRootOptions,
15+
} from './ReactDOMRoot';
16+
17+
import {
18+
createRoot as createRootImpl,
19+
hydrateRoot as hydrateRootImpl,
20+
isValidContainer,
21+
} from './ReactDOMRoot';
22+
import {createEventHandle} from 'react-dom-bindings/src/client/ReactDOMEventHandle';
23+
24+
import {
25+
flushSyncFromReconciler as flushSyncWithoutWarningIfAlreadyRendering,
26+
isAlreadyRendering,
27+
injectIntoDevTools,
28+
findHostInstance,
29+
} from 'react-reconciler/src/ReactFiberReconciler';
30+
import {runWithPriority} from 'react-reconciler/src/ReactEventPriorities';
31+
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
32+
import {canUseDOM} from 'shared/ExecutionEnvironment';
33+
import ReactVersion from 'shared/ReactVersion';
34+
35+
import {
36+
getClosestInstanceFromNode,
37+
getInstanceFromNode,
38+
getNodeFromInstance,
39+
getFiberCurrentPropsFromNode,
40+
} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
41+
import {
42+
enqueueStateRestore,
43+
restoreStateIfNeeded,
44+
} from 'react-dom-bindings/src/events/ReactDOMControlledComponent';
45+
import Internals from '../ReactDOMSharedInternals';
46+
47+
export {
48+
prefetchDNS,
49+
preconnect,
50+
preload,
51+
preloadModule,
52+
preinit,
53+
preinitModule,
54+
} from '../shared/ReactDOMFloat';
55+
export {
56+
useFormStatus,
57+
useFormState,
58+
} from 'react-dom-bindings/src/shared/ReactDOMFormActions';
59+
60+
if (__DEV__) {
61+
if (
62+
typeof Map !== 'function' ||
63+
// $FlowFixMe[prop-missing] Flow incorrectly thinks Map has no prototype
64+
Map.prototype == null ||
65+
typeof Map.prototype.forEach !== 'function' ||
66+
typeof Set !== 'function' ||
67+
// $FlowFixMe[prop-missing] Flow incorrectly thinks Set has no prototype
68+
Set.prototype == null ||
69+
typeof Set.prototype.clear !== 'function' ||
70+
typeof Set.prototype.forEach !== 'function'
71+
) {
72+
console.error(
73+
'React depends on Map and Set built-in types. Make sure that you load a ' +
74+
'polyfill in older browsers. https://react.dev/link/react-polyfills',
75+
);
76+
}
77+
}
78+
79+
function createPortal(
80+
children: ReactNodeList,
81+
container: Element | DocumentFragment,
82+
key: ?string = null,
83+
): React$Portal {
84+
if (!isValidContainer(container)) {
85+
throw new Error('Target container is not a DOM element.');
86+
}
87+
88+
// TODO: pass ReactDOM portal implementation as third argument
89+
// $FlowFixMe[incompatible-return] The Flow type is opaque but there's no way to actually create it.
90+
return createPortalImpl(children, container, null, key);
91+
}
92+
93+
function createRoot(
94+
container: Element | Document | DocumentFragment,
95+
options?: CreateRootOptions,
96+
): RootType {
97+
if (__DEV__) {
98+
if (!Internals.usingClientEntryPoint && !__UMD__) {
99+
console.error(
100+
'You are importing createRoot from "react-dom" which is not supported. ' +
101+
'You should instead import it from "react-dom/client".',
102+
);
103+
}
104+
}
105+
return createRootImpl(container, options);
106+
}
107+
108+
function hydrateRoot(
109+
container: Document | Element,
110+
initialChildren: ReactNodeList,
111+
options?: HydrateRootOptions,
112+
): RootType {
113+
if (__DEV__) {
114+
if (!Internals.usingClientEntryPoint && !__UMD__) {
115+
console.error(
116+
'You are importing hydrateRoot from "react-dom" which is not supported. ' +
117+
'You should instead import it from "react-dom/client".',
118+
);
119+
}
120+
}
121+
return hydrateRootImpl(container, initialChildren, options);
122+
}
123+
124+
// Overload the definition to the two valid signatures.
125+
// Warning, this opts-out of checking the function body.
126+
declare function flushSync<R>(fn: () => R): R;
127+
// eslint-disable-next-line no-redeclare
128+
declare function flushSync(): void;
129+
// eslint-disable-next-line no-redeclare
130+
function flushSync<R>(fn: (() => R) | void): R | void {
131+
if (__DEV__) {
132+
if (isAlreadyRendering()) {
133+
console.error(
134+
'flushSync was called from inside a lifecycle method. React cannot ' +
135+
'flush when React is already rendering. Consider moving this call to ' +
136+
'a scheduler task or micro task.',
137+
);
138+
}
139+
}
140+
return flushSyncWithoutWarningIfAlreadyRendering(fn);
141+
}
142+
143+
function findDOMNode(
144+
componentOrElement: React$Component<any, any>,
145+
): null | Element | Text {
146+
return findHostInstance(componentOrElement);
147+
}
148+
149+
// Expose findDOMNode on internals
150+
Internals.findDOMNode = findDOMNode;
151+
152+
function unstable_batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
153+
// batchedUpdates was a legacy mode feature that is a no-op outside of
154+
// legacy mode. In 19, we made it an actual no-op, but we're keeping it
155+
// for now since there may be libraries that still include it.
156+
return fn(a);
157+
}
158+
159+
export {
160+
createPortal,
161+
unstable_batchedUpdates,
162+
flushSync,
163+
ReactVersion as version,
164+
// exposeConcurrentModeAPIs
165+
createRoot,
166+
hydrateRoot,
167+
// enableCreateEventHandleAPI
168+
createEventHandle as unstable_createEventHandle,
169+
// TODO: Remove this once callers migrate to alternatives.
170+
// This should only be used by React internals.
171+
runWithPriority as unstable_runWithPriority,
172+
};
173+
174+
// Keep in sync with ReactTestUtils.js.
175+
// This is an array for better minification.
176+
Internals.Events = [
177+
getInstanceFromNode,
178+
getNodeFromInstance,
179+
getFiberCurrentPropsFromNode,
180+
enqueueStateRestore,
181+
restoreStateIfNeeded,
182+
unstable_batchedUpdates,
183+
];
184+
185+
const foundDevTools = injectIntoDevTools({
186+
findFiberByHostInstance: getClosestInstanceFromNode,
187+
bundleType: __DEV__ ? 1 : 0,
188+
version: ReactVersion,
189+
rendererPackageName: 'react-dom',
190+
});
191+
192+
if (__DEV__) {
193+
if (!foundDevTools && canUseDOM && window.top === window.self) {
194+
// If we're in Chrome or Firefox, provide a download link if not installed.
195+
if (
196+
(navigator.userAgent.indexOf('Chrome') > -1 &&
197+
navigator.userAgent.indexOf('Edge') === -1) ||
198+
navigator.userAgent.indexOf('Firefox') > -1
199+
) {
200+
const protocol = window.location.protocol;
201+
// Don't warn in exotic cases like chrome-extension://.
202+
if (/^(https?|file):$/.test(protocol)) {
203+
// eslint-disable-next-line react-internal/no-production-logging
204+
console.info(
205+
'%cDownload the React DevTools ' +
206+
'for a better development experience: ' +
207+
'https://react.dev/link/react-devtools' +
208+
(protocol === 'file:'
209+
? '\nYou might need to use a local HTTP server (instead of file://): ' +
210+
'https://react.dev/link/react-devtools-faq'
211+
: ''),
212+
'font-weight:bold',
213+
);
214+
}
215+
}
216+
}
217+
}

packages/react-dom/src/client/ReactDOMRoot.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ import {
1919
allowConcurrentByDefault,
2020
disableCommentsAsDOMContainers,
2121
enableAsyncActions,
22-
disableLegacyMode,
2322
} from 'shared/ReactFeatureFlags';
24-
import {flushSync} from '../shared/ReactDOMFlushSync';
2523

2624
export type RootType = {
2725
render(children: ReactNodeList): void,

packages/react-dom/src/shared/ReactDOMTypes.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ export type PreinitModuleScriptOptions = {
8282
};
8383

8484
export type HostDispatcher = {
85-
flushSyncWork: () => boolean,
85+
flushSyncWork: () => boolean | void,
8686
prefetchDNS: (href: string) => void,
8787
preconnect: (href: string, crossOrigin?: ?CrossOriginEnum) => void,
8888
preload: (href: string, as: string, options?: ?PreloadImplOptions) => void,

0 commit comments

Comments
 (0)