Move ReactDOMLegacy implementation into RootFB (#28656)
Only the FB entry point has legacy mode now so we can move the remaining code in there. Also enable disableLegacyMode in modern www builds since it doesn't expose those entry points. Now dependent on #28709. --------- Co-authored-by: Josh Story <story@hey.com>
Sebastian Markbåge committed
Apr 2, 2024 at 21:56 UTC
8f55a6aa5739ed8ca80c3066fb54f4ea4cfe600a
21 files changed
+206
-539
packages/react-devtools-shell/src/app/index.js
+3
@@ -69,6 +69,7 @@ function mountStrictApp(App) {
69
}
70
71
function mountLegacyApp(App: () => React$Node) {
72
+ // $FlowFixMe[prop-missing]: These are removed in 19.
73
const {render, unmountComponentAtNode} = require('react-dom');
74
75
function LegacyRender() {
@@ -77,8 +78,10 @@ function mountLegacyApp(App: () => React$Node) {
78
79
const container = createContainer();
80
81
+ // $FlowFixMe[not-a-function]: These are removed in 19.
82
render(createElement(LegacyRender), container);
83
84
+ // $FlowFixMe: These are removed in 19.
85
unmountFunctions.push(() => unmountComponentAtNode(container));
86
}
87
packages/react-devtools-shell/src/e2e-regression/app-legacy.js
+1
@@ -15,6 +15,7 @@ function mountApp(App: () => React$Node) {
15
16
((document.body: any): HTMLBodyElement).appendChild(container);
17
18
+ // $FlowFixMe[prop-missing]: These are removed in 19.
19
ReactDOM.render(<App />, container);
20
}
21
function mountTestApp() {
packages/react-dom/index.classic.fb.js
+3
-3
@@ -20,11 +20,8 @@ Object.assign((Internals: any), {
20
21
export {
22
createPortal,
23
- findDOMNode,
23
flushSync,
25
- unmountComponentAtNode,
24
unstable_createEventHandle,
27
- unstable_renderSubtreeIntoContainer,
25
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
26
useFormStatus,
27
useFormState,
@@ -42,6 +39,9 @@ export {
39
hydrateRoot,
40
render,
41
unstable_batchedUpdates,
42
+ findDOMNode,
43
+ unstable_renderSubtreeIntoContainer,
44
+ unmountComponentAtNode,
45
} from './src/client/ReactDOMRootFB';
46
47
export {Internals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED};
packages/react-dom/index.js
-3
@@ -15,11 +15,8 @@ export {
15
createRoot,
16
hydrateRoot,
17
flushSync,
18
- render,
19
- unmountComponentAtNode,
18
unstable_batchedUpdates,
19
unstable_createEventHandle,
22
- unstable_renderSubtreeIntoContainer,
20
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
21
useFormStatus,
22
useFormState,
packages/react-dom/index.stable.js
-3
@@ -13,10 +13,7 @@ export {
13
createRoot,
14
hydrateRoot,
15
flushSync,
16
- render,
17
- unmountComponentAtNode,
16
unstable_batchedUpdates,
19
- unstable_renderSubtreeIntoContainer,
17
useFormStatus,
18
useFormState,
19
prefetchDNS,
packages/react-dom/src/ReactDOMSharedInternals.js
+5
-2
@@ -7,7 +7,6 @@
7
* @flow
8
*/
9
10
-import type {FindDOMNodeType} from './client/ReactDOMLegacy.js';
10
import type {HostDispatcher} from './shared/ReactDOMTypes';
11
12
type InternalsType = {
@@ -16,7 +15,11 @@ type InternalsType = {
15
ReactDOMCurrentDispatcher: {
16
current: HostDispatcher,
17
},
19
- findDOMNode: null | FindDOMNodeType,
18
+ findDOMNode:
19
+ | null
20
+ | ((
21
+ componentOrElement: React$Component<any, any>,
22
+ ) => null | Element | Text),
23
};
24
25
function noop() {}
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
+2
-4
@@ -14,7 +14,6 @@ let act;
14
let React;
15
let ReactDOM;
16
let ReactDOMClient;
17
-let findDOMNode;
17
18
const clone = function (o) {
19
return JSON.parse(JSON.stringify(o));
@@ -95,8 +94,6 @@ describe('ReactComponentLifeCycle', () => {
94
95
React = require('react');
96
ReactDOM = require('react-dom');
98
- findDOMNode =
99
- ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.findDOMNode;
97
ReactDOMClient = require('react-dom/client');
98
});
99
@@ -376,6 +373,7 @@ describe('ReactComponentLifeCycle', () => {
373
expect(instance.updater.isMounted(instance)).toBe(false);
374
});
375
376
+ // @gate www && !disableLegacyMode
377
it('warns if legacy findDOMNode is used inside render', async () => {
378
class Component extends React.Component {
379
state = {isMounted: false};
@@ -384,7 +382,7 @@ describe('ReactComponentLifeCycle', () => {
382
}
383
render() {
384
if (this.state.isMounted) {
387
- expect(findDOMNode(this).tagName).toBe('DIV');
385
+ expect(ReactDOM.findDOMNode(this).tagName).toBe('DIV');
386
}
387
return <div />;
388
}
packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js
+3
-3
@@ -103,9 +103,9 @@ describe('ReactDeprecationWarnings', () => {
103
});
104
}
105
}
106
- expect(() => {
107
- ReactNoop.renderLegacySyncRoot(<Component />);
108
- }).toErrorDev([
106
+
107
+ ReactNoop.render(<Component />);
108
+ await expect(async () => await waitForAll([])).toErrorDev([
109
'Component "Component" contains the string ref "refComponent". Support for string refs will be removed in a future major release.',
110
]);
111
await waitForAll([]);
packages/react-dom/src/__tests__/ReactTestUtils-test.js
+1
@@ -586,6 +586,7 @@ describe('ReactTestUtils', () => {
586
});
587
588
// @gate !disableDOMTestUtils
589
+ // @gate !disableLegacyMode
590
it('should call setState callback with no arguments', async () => {
591
let mockArgs;
592
class Component extends React.Component {
packages/react-dom/src/__tests__/findDOMNodeFB-test.js
renamed
+18
-18
@@ -11,16 +11,15 @@
11
12
const React = require('react');
13
const ReactDOM = require('react-dom');
14
-const findDOMNode =
15
- ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.findDOMNode;
14
const StrictMode = React.StrictMode;
15
16
describe('findDOMNode', () => {
17
+ // @gate www && !disableLegacyMode
18
it('findDOMNode should return null if passed null', () => {
20
- expect(findDOMNode(null)).toBe(null);
19
+ expect(ReactDOM.findDOMNode(null)).toBe(null);
20
});
21
23
- // @gate !disableLegacyMode
22
+ // @gate www && !disableLegacyMode
23
it('findDOMNode should find dom element', () => {
24
class MyNode extends React.Component {
25
render() {
@@ -34,13 +33,13 @@ describe('findDOMNode', () => {
33
34
const container = document.createElement('div');
35
const myNode = ReactDOM.render(<MyNode />, container);
37
- const myDiv = findDOMNode(myNode);
38
- const mySameDiv = findDOMNode(myDiv);
36
+ const myDiv = ReactDOM.findDOMNode(myNode);
37
+ const mySameDiv = ReactDOM.findDOMNode(myDiv);
38
expect(myDiv.tagName).toBe('DIV');
39
expect(mySameDiv).toBe(myDiv);
40
});
41
43
- // @gate !disableLegacyMode
42
+ // @gate www && !disableLegacyMode
43
it('findDOMNode should find dom element after an update from null', () => {
44
function Bar({flag}) {
45
if (flag) {
@@ -57,23 +56,24 @@ describe('findDOMNode', () => {
56
const container = document.createElement('div');
57
58
const myNodeA = ReactDOM.render(<MyNode />, container);
60
- const a = findDOMNode(myNodeA);
59
+ const a = ReactDOM.findDOMNode(myNodeA);
60
expect(a).toBe(null);
61
62
const myNodeB = ReactDOM.render(<MyNode flag={true} />, container);
63
expect(myNodeA === myNodeB).toBe(true);
64
66
- const b = findDOMNode(myNodeB);
65
+ const b = ReactDOM.findDOMNode(myNodeB);
66
expect(b.tagName).toBe('SPAN');
67
});
68
69
+ // @gate www && !disableLegacyMode
70
it('findDOMNode should reject random objects', () => {
71
expect(function () {
72
- findDOMNode({foo: 'bar'});
72
+ ReactDOM.findDOMNode({foo: 'bar'});
73
}).toThrowError('Argument appears to not be a ReactComponent. Keys: foo');
74
});
75
76
- // @gate !disableLegacyMode
76
+ // @gate www && !disableLegacyMode
77
it('findDOMNode should reject unmounted objects with render func', () => {
78
class Foo extends React.Component {
79
render() {
@@ -85,16 +85,16 @@ describe('findDOMNode', () => {
85
const inst = ReactDOM.render(<Foo />, container);
86
ReactDOM.unmountComponentAtNode(container);
87
88
- expect(() => findDOMNode(inst)).toThrowError(
88
+ expect(() => ReactDOM.findDOMNode(inst)).toThrowError(
89
'Unable to find node on an unmounted component.',
90
);
91
});
92
93
- // @gate !disableLegacyMode
93
+ // @gate www && !disableLegacyMode
94
it('findDOMNode should not throw an error when called within a component that is not mounted', () => {
95
class Bar extends React.Component {
96
UNSAFE_componentWillMount() {
97
- expect(findDOMNode(this)).toBeNull();
97
+ expect(ReactDOM.findDOMNode(this)).toBeNull();
98
}
99
100
render() {
@@ -107,7 +107,7 @@ describe('findDOMNode', () => {
107
}).not.toThrow();
108
});
109
110
- // @gate !disableLegacyMode
110
+ // @gate www && !disableLegacyMode
111
it('findDOMNode should warn if used to find a host component inside StrictMode', () => {
112
let parent = undefined;
113
let child = undefined;
@@ -129,7 +129,7 @@ describe('findDOMNode', () => {
129
);
130
131
let match;
132
- expect(() => (match = findDOMNode(parent))).toErrorDev([
132
+ expect(() => (match = ReactDOM.findDOMNode(parent))).toErrorDev([
133
'Warning: findDOMNode is deprecated in StrictMode. ' +
134
'findDOMNode was passed an instance of ContainsStrictModeChild which renders StrictMode children. ' +
135
'Instead, add a ref directly to the element you want to reference. ' +
@@ -141,7 +141,7 @@ describe('findDOMNode', () => {
141
expect(match).toBe(child);
142
});
143
144
- // @gate !disableLegacyMode
144
+ // @gate www && !disableLegacyMode
145
it('findDOMNode should warn if passed a component that is inside StrictMode', () => {
146
let parent = undefined;
147
let child = undefined;
@@ -162,7 +162,7 @@ describe('findDOMNode', () => {
162
);
163
164
let match;
165
- expect(() => (match = findDOMNode(parent))).toErrorDev([
165
+ expect(() => (match = ReactDOM.findDOMNode(parent))).toErrorDev([
166
'Warning: findDOMNode is deprecated in StrictMode. ' +
167
'findDOMNode was passed an instance of IsInStrictMode which is inside StrictMode. ' +
168
'Instead, add a ref directly to the element you want to reference. ' +
packages/react-dom/src/client/ReactDOM.js
+7
-30
@@ -8,22 +8,12 @@
8
*/
9
10
import type {ReactNodeList} from 'shared/ReactTypes';
11
-import type {
12
- Container,
13
- PublicInstance,
14
-} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
11
import type {
12
RootType,
13
HydrateRootOptions,
14
CreateRootOptions,
15
} from './ReactDOMRoot';
16
21
-import {
22
- findDOMNode,
23
- render,
24
- unstable_renderSubtreeIntoContainer,
25
- unmountComponentAtNode,
26
-} from './ReactDOMLegacy';
17
import {
18
createRoot as createRootImpl,
19
hydrateRoot as hydrateRootImpl,
@@ -35,6 +25,7 @@ import {
25
flushSync 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';
@@ -99,20 +90,6 @@ function createPortal(
90
return createPortalImpl(children, container, null, key);
91
}
92
102
-function renderSubtreeIntoContainer(
103
- parentComponent: React$Component<any, any>,
104
- element: React$Element<any>,
105
- containerNode: Container,
106
- callback: ?Function,
107
-): React$Component<any, any> | PublicInstance | null {
108
- return unstable_renderSubtreeIntoContainer(
109
- parentComponent,
110
- element,
111
- containerNode,
112
- callback,
113
- );
114
-}
115
-
93
function createRoot(
94
container: Element | Document | DocumentFragment,
95
options?: CreateRootOptions,
@@ -163,6 +140,12 @@ function flushSync<R>(fn: (() => R) | void): R | void {
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
@@ -178,15 +161,9 @@ export {
161
unstable_batchedUpdates,
162
flushSync,
163
ReactVersion as version,
181
- // Disabled behind disableLegacyReactDOMAPIs
182
- findDOMNode,
183
- render,
184
- unmountComponentAtNode,
164
// exposeConcurrentModeAPIs
165
createRoot,
166
hydrateRoot,
188
- // Disabled behind disableUnstableRenderSubtreeIntoContainer
189
- renderSubtreeIntoContainer as unstable_renderSubtreeIntoContainer,
167
// enableCreateEventHandleAPI
168
createEventHandle as unstable_createEventHandle,
169
// TODO: Remove this once callers migrate to alternatives.
packages/react-dom/src/client/ReactDOMLegacy.js
deleted
-434
@@ -1,434 +0,0 @@
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 {
11
- Container,
12
- PublicInstance,
13
-} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
14
-import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
15
-import type {ReactNodeList} from 'shared/ReactTypes';
16
-
17
-import {disableLegacyMode} from 'shared/ReactFeatureFlags';
18
-import {clearContainer} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
19
-import {
20
- getInstanceFromNode,
21
- isContainerMarkedAsRoot,
22
- markContainerAsRoot,
23
- unmarkContainerAsRoot,
24
-} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
25
-import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
26
-import {isValidContainerLegacy} from './ReactDOMRoot';
27
-import {
28
- DOCUMENT_NODE,
29
- ELEMENT_NODE,
30
- COMMENT_NODE,
31
-} from 'react-dom-bindings/src/client/HTMLNodeType';
32
-
33
-import {
34
- createContainer,
35
- createHydrationContainer,
36
- findHostInstanceWithNoPortals,
37
- updateContainer,
38
- flushSync,
39
- getPublicRootInstance,
40
- findHostInstance,
41
- findHostInstanceWithWarning,
42
- defaultOnUncaughtError,
43
- defaultOnCaughtError,
44
-} from 'react-reconciler/src/ReactFiberReconciler';
45
-import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
46
-import getComponentNameFromType from 'shared/getComponentNameFromType';
47
-import ReactSharedInternals from 'shared/ReactSharedInternals';
48
-import {has as hasInstance} from 'shared/ReactInstanceMap';
49
-
50
-const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
51
-
52
-let topLevelUpdateWarnings;
53
-
54
-if (__DEV__) {
55
- topLevelUpdateWarnings = (container: Container) => {
56
- if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) {
57
- const hostInstance = findHostInstanceWithNoPortals(
58
- container._reactRootContainer.current,
59
- );
60
- if (hostInstance) {
61
- if (hostInstance.parentNode !== container) {
62
- console.error(
63
- 'It looks like the React-rendered content of this ' +
64
- 'container was removed without using React. This is not ' +
65
- 'supported and will cause errors. Instead, call ' +
66
- 'ReactDOM.unmountComponentAtNode to empty a container.',
67
- );
68
- }
69
- }
70
- }
71
-
72
- const isRootRenderedBySomeReact = !!container._reactRootContainer;
73
- const rootEl = getReactRootElementInContainer(container);
74
- const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
75
-
76
- if (hasNonRootReactChild && !isRootRenderedBySomeReact) {
77
- console.error(
78
- 'Replacing React-rendered children with a new root ' +
79
- 'component. If you intended to update the children of this node, ' +
80
- 'you should instead have the existing children update their state ' +
81
- 'and render the new components instead of calling ReactDOM.render.',
82
- );
83
- }
84
- };
85
-}
86
-
87
-function getReactRootElementInContainer(container: any) {
88
- if (!container) {
89
- return null;
90
- }
91
-
92
- if (container.nodeType === DOCUMENT_NODE) {
93
- return container.documentElement;
94
- } else {
95
- return container.firstChild;
96
- }
97
-}
98
-
99
-function noopOnRecoverableError() {
100
- // This isn't reachable because onRecoverableError isn't called in the
101
- // legacy API.
102
-}
103
-
104
-function legacyCreateRootFromDOMContainer(
105
- container: Container,
106
- initialChildren: ReactNodeList,
107
- parentComponent: ?React$Component<any, any>,
108
- callback: ?Function,
109
- isHydrationContainer: boolean,
110
-): FiberRoot {
111
- if (isHydrationContainer) {
112
- if (typeof callback === 'function') {
113
- const originalCallback = callback;
114
- callback = function () {
115
- const instance = getPublicRootInstance(root);
116
- originalCallback.call(instance);
117
- };
118
- }
119
-
120
- const root: FiberRoot = createHydrationContainer(
121
- initialChildren,
122
- callback,
123
- container,
124
- LegacyRoot,
125
- null, // hydrationCallbacks
126
- false, // isStrictMode
127
- false, // concurrentUpdatesByDefaultOverride,
128
- '', // identifierPrefix
129
- defaultOnUncaughtError,
130
- defaultOnCaughtError,
131
- noopOnRecoverableError,
132
- // TODO(luna) Support hydration later
133
- null,
134
- null,
135
- );
136
- container._reactRootContainer = root;
137
- markContainerAsRoot(root.current, container);
138
-
139
- const rootContainerElement =
140
- container.nodeType === COMMENT_NODE ? container.parentNode : container;
141
- // $FlowFixMe[incompatible-call]
142
- listenToAllSupportedEvents(rootContainerElement);
143
-
144
- flushSync();
145
- return root;
146
- } else {
147
- // First clear any existing content.
148
- clearContainer(container);
149
-
150
- if (typeof callback === 'function') {
151
- const originalCallback = callback;
152
- callback = function () {
153
- const instance = getPublicRootInstance(root);
154
- originalCallback.call(instance);
155
- };
156
- }
157
-
158
- const root = createContainer(
159
- container,
160
- LegacyRoot,
161
- null, // hydrationCallbacks
162
- false, // isStrictMode
163
- false, // concurrentUpdatesByDefaultOverride,
164
- '', // identifierPrefix
165
- defaultOnUncaughtError,
166
- defaultOnCaughtError,
167
- noopOnRecoverableError,
168
- null, // transitionCallbacks
169
- );
170
- container._reactRootContainer = root;
171
- markContainerAsRoot(root.current, container);
172
-
173
- const rootContainerElement =
174
- container.nodeType === COMMENT_NODE ? container.parentNode : container;
175
- // $FlowFixMe[incompatible-call]
176
- listenToAllSupportedEvents(rootContainerElement);
177
-
178
- // Initial mount should not be batched.
179
- flushSync(() => {
180
- updateContainer(initialChildren, root, parentComponent, callback);
181
- });
182
-
183
- return root;
184
- }
185
-}
186
-
187
-function warnOnInvalidCallback(callback: mixed): void {
188
- if (__DEV__) {
189
- if (callback !== null && typeof callback !== 'function') {
190
- console.error(
191
- 'Expected the last optional `callback` argument to be a ' +
192
- 'function. Instead received: %s.',
193
- callback,
194
- );
195
- }
196
- }
197
-}
198
-
199
-function legacyRenderSubtreeIntoContainer(
200
- parentComponent: ?React$Component<any, any>,
201
- children: ReactNodeList,
202
- container: Container,
203
- forceHydrate: boolean,
204
- callback: ?Function,
205
-): React$Component<any, any> | PublicInstance | null {
206
- if (__DEV__) {
207
- topLevelUpdateWarnings(container);
208
- warnOnInvalidCallback(callback === undefined ? null : callback);
209
- }
210
-
211
- const maybeRoot = container._reactRootContainer;
212
- let root: FiberRoot;
213
- if (!maybeRoot) {
214
- // Initial mount
215
- root = legacyCreateRootFromDOMContainer(
216
- container,
217
- children,
218
- parentComponent,
219
- callback,
220
- forceHydrate,
221
- );
222
- } else {
223
- root = maybeRoot;
224
- if (typeof callback === 'function') {
225
- const originalCallback = callback;
226
- callback = function () {
227
- const instance = getPublicRootInstance(root);
228
- originalCallback.call(instance);
229
- };
230
- }
231
- // Update
232
- updateContainer(children, root, parentComponent, callback);
233
- }
234
- return getPublicRootInstance(root);
235
-}
236
-
237
-export type FindDOMNodeType = typeof findDOMNode;
238
-
239
-export function findDOMNode(
240
- componentOrElement: Element | ?React$Component<any, any>,
241
-): null | Element | Text {
242
- if (__DEV__) {
243
- const owner = (ReactCurrentOwner.current: any);
244
- if (owner !== null && owner.stateNode !== null) {
245
- const warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender;
246
- if (!warnedAboutRefsInRender) {
247
- console.error(
248
- '%s is accessing findDOMNode inside its render(). ' +
249
- 'render() should be a pure function of props and state. It should ' +
250
- 'never access something that requires stale data from the previous ' +
251
- 'render, such as refs. Move this logic to componentDidMount and ' +
252
- 'componentDidUpdate instead.',
253
- getComponentNameFromType(owner.type) || 'A component',
254
- );
255
- }
256
- owner.stateNode._warnedAboutRefsInRender = true;
257
- }
258
- }
259
- if (componentOrElement == null) {
260
- return null;
261
- }
262
- if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
263
- return (componentOrElement: any);
264
- }
265
- if (__DEV__) {
266
- return findHostInstanceWithWarning(componentOrElement, 'findDOMNode');
267
- }
268
- return findHostInstance(componentOrElement);
269
-}
270
-
271
-export function render(
272
- element: React$Element<any>,
273
- container: Container,
274
- callback: ?Function,
275
-): React$Component<any, any> | PublicInstance | null {
276
- if (disableLegacyMode) {
277
- if (__DEV__) {
278
- console.error(
279
- 'ReactDOM.render was removed in React 19. Use createRoot instead.',
280
- );
281
- }
282
- throw new Error('ReactDOM: Unsupported Legacy Mode API.');
283
- }
284
- if (__DEV__) {
285
- console.error(
286
- 'ReactDOM.render has not been supported since React 18. Use createRoot ' +
287
- 'instead. Until you switch to the new API, your app will behave as ' +
288
- "if it's running React 17. Learn " +
289
- 'more: https://react.dev/link/switch-to-createroot',
290
- );
291
- }
292
-
293
- if (!isValidContainerLegacy(container)) {
294
- throw new Error('Target container is not a DOM element.');
295
- }
296
-
297
- if (__DEV__) {
298
- const isModernRoot =
299
- isContainerMarkedAsRoot(container) &&
300
- container._reactRootContainer === undefined;
301
- if (isModernRoot) {
302
- console.error(
303
- 'You are calling ReactDOM.render() on a container that was previously ' +
304
- 'passed to ReactDOMClient.createRoot(). This is not supported. ' +
305
- 'Did you mean to call root.render(element)?',
306
- );
307
- }
308
- }
309
- return legacyRenderSubtreeIntoContainer(
310
- null,
311
- element,
312
- container,
313
- false,
314
- callback,
315
- );
316
-}
317
-
318
-export function unstable_renderSubtreeIntoContainer(
319
- parentComponent: React$Component<any, any>,
320
- element: React$Element<any>,
321
- containerNode: Container,
322
- callback: ?Function,
323
-): React$Component<any, any> | PublicInstance | null {
324
- if (disableLegacyMode) {
325
- if (__DEV__) {
326
- console.error(
327
- 'ReactDOM.unstable_renderSubtreeIntoContainer() was removed in React 19. Consider using a portal instead.',
328
- );
329
- }
330
- throw new Error('ReactDOM: Unsupported Legacy Mode API.');
331
- }
332
- if (__DEV__) {
333
- console.error(
334
- 'ReactDOM.unstable_renderSubtreeIntoContainer() has not been supported ' +
335
- 'since React 18. Consider using a portal instead. Until you switch to ' +
336
- "the createRoot API, your app will behave as if it's running React " +
337
- '17. Learn more: https://react.dev/link/switch-to-createroot',
338
- );
339
- }
340
-
341
- if (!isValidContainerLegacy(containerNode)) {
342
- throw new Error('Target container is not a DOM element.');
343
- }
344
-
345
- if (parentComponent == null || !hasInstance(parentComponent)) {
346
- throw new Error('parentComponent must be a valid React Component');
347
- }
348
-
349
- return legacyRenderSubtreeIntoContainer(
350
- parentComponent,
351
- element,
352
- containerNode,
353
- false,
354
- callback,
355
- );
356
-}
357
-
358
-export function unmountComponentAtNode(container: Container): boolean {
359
- if (disableLegacyMode) {
360
- if (__DEV__) {
361
- console.error(
362
- 'unmountComponentAtNode was removed in React 19. Use root.unmount() instead.',
363
- );
364
- }
365
- throw new Error('ReactDOM: Unsupported Legacy Mode API.');
366
- }
367
- if (!isValidContainerLegacy(container)) {
368
- throw new Error('Target container is not a DOM element.');
369
- }
370
-
371
- if (__DEV__) {
372
- const isModernRoot =
373
- isContainerMarkedAsRoot(container) &&
374
- container._reactRootContainer === undefined;
375
- if (isModernRoot) {
376
- console.error(
377
- 'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
378
- 'passed to ReactDOMClient.createRoot(). This is not supported. Did you mean to call root.unmount()?',
379
- );
380
- }
381
- }
382
-
383
- if (container._reactRootContainer) {
384
- if (__DEV__) {
385
- const rootEl = getReactRootElementInContainer(container);
386
- const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
387
- if (renderedByDifferentReact) {
388
- console.error(
389
- "unmountComponentAtNode(): The node you're attempting to unmount " +
390
- 'was rendered by another copy of React.',
391
- );
392
- }
393
- }
394
-
395
- // Unmount should not be batched.
396
- flushSync(() => {
397
- legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
398
- // $FlowFixMe[incompatible-type] This should probably use `delete container._reactRootContainer`
399
- container._reactRootContainer = null;
400
- unmarkContainerAsRoot(container);
401
- });
402
- });
403
- // If you call unmountComponentAtNode twice in quick succession, you'll
404
- // get `true` twice. That's probably fine?
405
- return true;
406
- } else {
407
- if (__DEV__) {
408
- const rootEl = getReactRootElementInContainer(container);
409
- const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
410
-
411
- // Check if the container itself is a React root node.
412
- const isContainerReactRoot =
413
- container.nodeType === ELEMENT_NODE &&
414
- isValidContainerLegacy(container.parentNode) &&
415
- // $FlowFixMe[prop-missing]
416
- // $FlowFixMe[incompatible-use]
417
- !!container.parentNode._reactRootContainer;
418
-
419
- if (hasNonRootReactChild) {
420
- console.error(
421
- "unmountComponentAtNode(): The node you're attempting to unmount " +
422
- 'was rendered by React and is not a top-level container. %s',
423
- isContainerReactRoot
424
- ? 'You may have accidentally passed in a React root node instead ' +
425
- 'of its container.'
426
- : 'Instead, have the parent component update its state and ' +
427
- 'rerender in order to remove this component.',
428
- );
429
- }
430
- }
431
-
432
- return false;
433
- }
434
-}
packages/react-dom/src/client/ReactDOMRootFB.js
+119
@@ -33,11 +33,13 @@ import {
33
getInstanceFromNode,
34
isContainerMarkedAsRoot,
35
markContainerAsRoot,
36
+ unmarkContainerAsRoot,
37
} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
38
import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
39
import {isValidContainerLegacy} from './ReactDOMRoot';
40
import {
41
DOCUMENT_NODE,
42
+ ELEMENT_NODE,
43
COMMENT_NODE,
44
} from 'react-dom-bindings/src/client/HTMLNodeType';
45
@@ -49,12 +51,17 @@ import {
51
updateContainer,
52
flushSync,
53
getPublicRootInstance,
54
+ findHostInstance,
55
+ findHostInstanceWithWarning,
56
defaultOnUncaughtError,
57
defaultOnCaughtError,
58
} from 'react-reconciler/src/ReactFiberReconciler';
59
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
60
+import getComponentNameFromType from 'shared/getComponentNameFromType';
61
import {has as hasInstance} from 'shared/ReactInstanceMap';
62
63
+import ReactSharedInternals from 'shared/ReactSharedInternals';
64
+
65
import assign from 'shared/assign';
66
67
// Provided by www
@@ -146,6 +153,8 @@ export function hydrateRoot(
153
);
154
}
155
156
+const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
157
+
158
let topLevelUpdateWarnings;
159
160
if (__DEV__) {
@@ -331,6 +340,38 @@ function legacyRenderSubtreeIntoContainer(
340
return getPublicRootInstance(root);
341
}
342
343
+export function findDOMNode(
344
+ componentOrElement: Element | ?React$Component<any, any>,
345
+): null | Element | Text {
346
+ if (__DEV__) {
347
+ const owner = (ReactCurrentOwner.current: any);
348
+ if (owner !== null && owner.stateNode !== null) {
349
+ const warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender;
350
+ if (!warnedAboutRefsInRender) {
351
+ console.error(
352
+ '%s is accessing findDOMNode inside its render(). ' +
353
+ 'render() should be a pure function of props and state. It should ' +
354
+ 'never access something that requires stale data from the previous ' +
355
+ 'render, such as refs. Move this logic to componentDidMount and ' +
356
+ 'componentDidUpdate instead.',
357
+ getComponentNameFromType(owner.type) || 'A component',
358
+ );
359
+ }
360
+ owner.stateNode._warnedAboutRefsInRender = true;
361
+ }
362
+ }
363
+ if (componentOrElement == null) {
364
+ return null;
365
+ }
366
+ if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
367
+ return (componentOrElement: any);
368
+ }
369
+ if (__DEV__) {
370
+ return findHostInstanceWithWarning(componentOrElement, 'findDOMNode');
371
+ }
372
+ return findHostInstance(componentOrElement);
373
+}
374
+
375
export function render(
376
element: React$Element<any>,
377
container: Container,
@@ -418,4 +459,82 @@ export function unstable_renderSubtreeIntoContainer(
459
);
460
}
461
462
+export function unmountComponentAtNode(container: Container): boolean {
463
+ if (disableLegacyMode) {
464
+ if (__DEV__) {
465
+ console.error(
466
+ 'unmountComponentAtNode was removed in React 19. Use root.unmount() instead.',
467
+ );
468
+ }
469
+ throw new Error('ReactDOM: Unsupported Legacy Mode API.');
470
+ }
471
+ if (!isValidContainerLegacy(container)) {
472
+ throw new Error('Target container is not a DOM element.');
473
+ }
474
+
475
+ if (__DEV__) {
476
+ const isModernRoot =
477
+ isContainerMarkedAsRoot(container) &&
478
+ container._reactRootContainer === undefined;
479
+ if (isModernRoot) {
480
+ console.error(
481
+ 'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
482
+ 'passed to ReactDOMClient.createRoot(). This is not supported. Did you mean to call root.unmount()?',
483
+ );
484
+ }
485
+ }
486
+
487
+ if (container._reactRootContainer) {
488
+ if (__DEV__) {
489
+ const rootEl = getReactRootElementInContainer(container);
490
+ const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
491
+ if (renderedByDifferentReact) {
492
+ console.error(
493
+ "unmountComponentAtNode(): The node you're attempting to unmount " +
494
+ 'was rendered by another copy of React.',
495
+ );
496
+ }
497
+ }
498
+
499
+ // Unmount should not be batched.
500
+ flushSync(() => {
501
+ legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
502
+ // $FlowFixMe[incompatible-type] This should probably use `delete container._reactRootContainer`
503
+ container._reactRootContainer = null;
504
+ unmarkContainerAsRoot(container);
505
+ });
506
+ });
507
+ // If you call unmountComponentAtNode twice in quick succession, you'll
508
+ // get `true` twice. That's probably fine?
509
+ return true;
510
+ } else {
511
+ if (__DEV__) {
512
+ const rootEl = getReactRootElementInContainer(container);
513
+ const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
514
+
515
+ // Check if the container itself is a React root node.
516
+ const isContainerReactRoot =
517
+ container.nodeType === ELEMENT_NODE &&
518
+ isValidContainerLegacy(container.parentNode) &&
519
+ // $FlowFixMe[prop-missing]
520
+ // $FlowFixMe[incompatible-use]
521
+ !!container.parentNode._reactRootContainer;
522
+
523
+ if (hasNonRootReactChild) {
524
+ console.error(
525
+ "unmountComponentAtNode(): The node you're attempting to unmount " +
526
+ 'was rendered by React and is not a top-level container. %s',
527
+ isContainerReactRoot
528
+ ? 'You may have accidentally passed in a React root node instead ' +
529
+ 'of its container.'
530
+ : 'Instead, have the parent component update its state and ' +
531
+ 'rerender in order to remove this component.',
532
+ );
533
+ }
534
+ }
535
+
536
+ return false;
537
+ }
538
+}
539
+
540
export {batchedUpdates as unstable_batchedUpdates};
packages/react-dom/unstable_testing.js
-3
@@ -10,11 +10,8 @@
10
export {
11
createPortal,
12
flushSync,
13
- render,
14
- unmountComponentAtNode,
13
unstable_batchedUpdates,
14
unstable_createEventHandle,
17
- unstable_renderSubtreeIntoContainer,
15
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
16
useFormStatus,
17
useFormState,
packages/react-dom/unstable_testing.stable.js
-3
@@ -10,10 +10,7 @@
10
export {
11
createPortal,
12
flushSync,
13
- render,
14
- unmountComponentAtNode,
13
unstable_batchedUpdates,
16
- unstable_renderSubtreeIntoContainer,
14
useFormStatus,
15
useFormState,
16
prefetchDNS,
packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
+38
-22
@@ -393,7 +393,7 @@ describe('ReactScope', () => {
393
});
394
395
// @gate www
396
- it('DO_NOT_USE_queryAllNodes() works as intended', () => {
396
+ it('DO_NOT_USE_queryAllNodes() works as intended', async () => {
397
const testScopeQuery = (type, props) => true;
398
const TestScope = React.unstable_Scope;
399
const scopeRef = React.createRef();
@@ -417,20 +417,25 @@ describe('ReactScope', () => {
417
);
418
}
419
420
- const renderer = ReactTestRenderer.create(<Test toggle={true} />, {
421
- createNodeMock: element => {
422
- return element;
423
- },
424
- });
420
+ let renderer;
421
+ await act(
422
+ () =>
423
+ (renderer = ReactTestRenderer.create(<Test toggle={true} />, {
424
+ createNodeMock: element => {
425
+ return element;
426
+ },
427
+ unstable_isConcurrent: true,
428
+ })),
429
+ );
430
let nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
431
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
427
- renderer.update(<Test toggle={false} />);
432
+ await act(() => renderer.update(<Test toggle={false} />));
433
nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
434
expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
435
});
436
437
// @gate www
433
- it('DO_NOT_USE_queryFirstNode() works as intended', () => {
438
+ it('DO_NOT_USE_queryFirstNode() works as intended', async () => {
439
const testScopeQuery = (type, props) => true;
440
const TestScope = React.unstable_Scope;
441
const scopeRef = React.createRef();
@@ -454,20 +459,26 @@ describe('ReactScope', () => {
459
);
460
}
461
457
- const renderer = ReactTestRenderer.create(<Test toggle={true} />, {
458
- createNodeMock: element => {
459
- return element;
460
- },
461
- });
462
+ let renderer;
463
+ await act(
464
+ () =>
465
+ (renderer = ReactTestRenderer.create(<Test toggle={true} />, {
466
+ createNodeMock: element => {
467
+ return element;
468
+ },
469
+ unstable_isConcurrent: true,
470
+ })),
471
+ );
472
let node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
473
expect(node).toEqual(divRef.current);
464
- renderer.update(<Test toggle={false} />);
474
+ await act(() => renderer.update(<Test toggle={false} />));
475
+
476
node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
477
expect(node).toEqual(aRef.current);
478
});
479
480
// @gate www
470
- it('containsNode() works as intended', () => {
481
+ it('containsNode() works as intended', async () => {
482
const TestScope = React.unstable_Scope;
483
const scopeRef = React.createRef();
484
const divRef = React.createRef();
@@ -500,23 +511,28 @@ describe('ReactScope', () => {
511
);
512
}
513
503
- const renderer = ReactTestRenderer.create(<Test toggle={true} />, {
504
- createNodeMock: element => {
505
- return element;
506
- },
507
- });
514
+ let renderer;
515
+ await act(
516
+ () =>
517
+ (renderer = ReactTestRenderer.create(<Test toggle={true} />, {
518
+ createNodeMock: element => {
519
+ return element;
520
+ },
521
+ unstable_isConcurrent: true,
522
+ })),
523
+ );
524
expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
525
expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
526
expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
527
expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
528
expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
513
- renderer.update(<Test toggle={false} />);
529
+ await act(() => renderer.update(<Test toggle={false} />));
530
expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
531
expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
532
expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
533
expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
534
expect(scopeRef.current.containsNode(emRef.current)).toBe(true);
519
- renderer.update(<Test toggle={true} />);
535
+ await act(() => renderer.update(<Test toggle={true} />));
536
expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
537
});
538
});
packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+1
-1
@@ -378,7 +378,7 @@ describe('ReactSuspense', () => {
378
expect(container.textContent).toEqual('AB');
379
});
380
381
- // @gate forceConcurrentByDefaultForTesting
381
+ // @gate !disableLegacyMode && forceConcurrentByDefaultForTesting
382
it(
383
'interrupts current render when something suspends with a ' +
384
"delay and we've already skipped over a lower priority update in " +
packages/react/src/__tests__/ReactJSXRuntime-test.js
+2
-7
@@ -10,12 +10,10 @@
10
'use strict';
11
12
let React;
13
-let ReactDOM;
13
let ReactDOMClient;
14
let JSXRuntime;
15
let JSXDEVRuntime;
16
let act;
18
-let findDOMNode;
17
18
// NOTE: Prefer to call the JSXRuntime directly in these tests so we can be
19
// certain that we are testing the runtime behavior, as opposed to the Babel
@@ -27,11 +25,8 @@ describe('ReactJSXRuntime', () => {
25
React = require('react');
26
JSXRuntime = require('react/jsx-runtime');
27
JSXDEVRuntime = require('react/jsx-dev-runtime');
30
- ReactDOM = require('react-dom');
28
ReactDOMClient = require('react-dom/client');
29
act = require('internal-test-utils').act;
33
- findDOMNode =
34
- ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.findDOMNode;
30
});
31
32
it('allows static methods to be called using the type property', () => {
@@ -133,9 +128,9 @@ describe('ReactJSXRuntime', () => {
128
129
const outer = container.firstChild;
130
if (__DEV__) {
136
- expect(findDOMNode(outer).className).toBe('moo');
131
+ expect(outer.className).toBe('moo');
132
} else {
138
- expect(findDOMNode(outer).className).toBe('quack');
133
+ expect(outer.className).toBe('quack');
134
}
135
});
136
packages/shared/ReactFeatureFlags.js
+1
-1
@@ -178,7 +178,7 @@ export const enableReactTestRendererWarning = __NEXT_MAJOR__;
178
// Disables legacy mode
179
// This allows us to land breaking changes to remove legacy mode APIs in experimental builds
180
// before removing them in stable in the next Major
181
-export const disableLegacyMode = __NEXT_MAJOR__;
181
+export const disableLegacyMode = true;
182
183
export const disableDOMTestUtils = __NEXT_MAJOR__;
184
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
-1
@@ -88,7 +88,7 @@ const __NEXT_MAJOR__ = __EXPERIMENTAL__;
88
export const enableRefAsProp = __NEXT_MAJOR__;
89
export const disableStringRefs = __NEXT_MAJOR__;
90
export const enableBigIntSupport = __NEXT_MAJOR__;
91
-export const disableLegacyMode = __NEXT_MAJOR__;
91
+export const disableLegacyMode = true;
92
export const disableLegacyContext = __NEXT_MAJOR__;
93
export const disableDOMTestUtils = __NEXT_MAJOR__;
94
export const enableRenderableContext = __NEXT_MAJOR__;
packages/shared/forks/ReactFeatureFlags.www.js
+1
-1
@@ -113,7 +113,7 @@ export const useModernStrictMode = true;
113
// because JSX is an extremely hot path.
114
export const disableStringRefs = false;
115
116
-export const disableLegacyMode = false;
116
+export const disableLegacyMode = __EXPERIMENTAL__;
117
118
export const disableDOMTestUtils = false;
119