@samitouri / QOS-React / commits / 8f55a6aa57

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