diff --git a/packages/react-devtools-shell/src/app/index.js b/packages/react-devtools-shell/src/app/index.js
index 69769f80d25a6..f991d61363bd1 100644
--- a/packages/react-devtools-shell/src/app/index.js
+++ b/packages/react-devtools-shell/src/app/index.js
@@ -69,6 +69,7 @@ function mountStrictApp(App) {
}
function mountLegacyApp(App: () => React$Node) {
+ // $FlowFixMe[prop-missing]: These are removed in 19.
const {render, unmountComponentAtNode} = require('react-dom');
function LegacyRender() {
@@ -77,8 +78,10 @@ function mountLegacyApp(App: () => React$Node) {
const container = createContainer();
+ // $FlowFixMe[not-a-function]: These are removed in 19.
render(createElement(LegacyRender), container);
+ // $FlowFixMe: These are removed in 19.
unmountFunctions.push(() => unmountComponentAtNode(container));
}
diff --git a/packages/react-devtools-shell/src/e2e-regression/app-legacy.js b/packages/react-devtools-shell/src/e2e-regression/app-legacy.js
index e8bb10fda091b..19ad2f0f6d953 100644
--- a/packages/react-devtools-shell/src/e2e-regression/app-legacy.js
+++ b/packages/react-devtools-shell/src/e2e-regression/app-legacy.js
@@ -15,6 +15,7 @@ function mountApp(App: () => React$Node) {
((document.body: any): HTMLBodyElement).appendChild(container);
+ // $FlowFixMe[prop-missing]: These are removed in 19.
ReactDOM.render(, container);
}
function mountTestApp() {
diff --git a/packages/react-dom/index.classic.fb.js b/packages/react-dom/index.classic.fb.js
index 5d4cb1e11f822..e765898d91cb8 100644
--- a/packages/react-dom/index.classic.fb.js
+++ b/packages/react-dom/index.classic.fb.js
@@ -20,11 +20,8 @@ Object.assign((Internals: any), {
export {
createPortal,
- findDOMNode,
flushSync,
- unmountComponentAtNode,
unstable_createEventHandle,
- unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus,
useFormState,
@@ -42,6 +39,9 @@ export {
hydrateRoot,
render,
unstable_batchedUpdates,
+ findDOMNode,
+ unstable_renderSubtreeIntoContainer,
+ unmountComponentAtNode,
} from './src/client/ReactDOMRootFB';
export {Internals as __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED};
diff --git a/packages/react-dom/index.js b/packages/react-dom/index.js
index 30ccf0a2eac89..4357044d90818 100644
--- a/packages/react-dom/index.js
+++ b/packages/react-dom/index.js
@@ -15,11 +15,8 @@ export {
createRoot,
hydrateRoot,
flushSync,
- render,
- unmountComponentAtNode,
unstable_batchedUpdates,
unstable_createEventHandle,
- unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus,
useFormState,
diff --git a/packages/react-dom/index.stable.js b/packages/react-dom/index.stable.js
index a817a6b3d9af3..6da13efa643cd 100644
--- a/packages/react-dom/index.stable.js
+++ b/packages/react-dom/index.stable.js
@@ -13,10 +13,7 @@ export {
createRoot,
hydrateRoot,
flushSync,
- render,
- unmountComponentAtNode,
unstable_batchedUpdates,
- unstable_renderSubtreeIntoContainer,
useFormStatus,
useFormState,
prefetchDNS,
diff --git a/packages/react-dom/src/ReactDOMSharedInternals.js b/packages/react-dom/src/ReactDOMSharedInternals.js
index 6de3648103591..7bd080ce48240 100644
--- a/packages/react-dom/src/ReactDOMSharedInternals.js
+++ b/packages/react-dom/src/ReactDOMSharedInternals.js
@@ -7,7 +7,6 @@
* @flow
*/
-import type {FindDOMNodeType} from './client/ReactDOMLegacy.js';
import type {HostDispatcher} from './shared/ReactDOMTypes';
type InternalsType = {
@@ -16,7 +15,11 @@ type InternalsType = {
ReactDOMCurrentDispatcher: {
current: HostDispatcher,
},
- findDOMNode: null | FindDOMNodeType,
+ findDOMNode:
+ | null
+ | ((
+ componentOrElement: React$Component,
+ ) => null | Element | Text),
};
function noop() {}
diff --git a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
index f89e95bcf5dde..4bea59a7b8e53 100644
--- a/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
+++ b/packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
@@ -14,7 +14,6 @@ let act;
let React;
let ReactDOM;
let ReactDOMClient;
-let findDOMNode;
const clone = function (o) {
return JSON.parse(JSON.stringify(o));
@@ -95,8 +94,6 @@ describe('ReactComponentLifeCycle', () => {
React = require('react');
ReactDOM = require('react-dom');
- findDOMNode =
- ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.findDOMNode;
ReactDOMClient = require('react-dom/client');
});
@@ -376,6 +373,7 @@ describe('ReactComponentLifeCycle', () => {
expect(instance.updater.isMounted(instance)).toBe(false);
});
+ // @gate www && !disableLegacyMode
it('warns if legacy findDOMNode is used inside render', async () => {
class Component extends React.Component {
state = {isMounted: false};
@@ -384,7 +382,7 @@ describe('ReactComponentLifeCycle', () => {
}
render() {
if (this.state.isMounted) {
- expect(findDOMNode(this).tagName).toBe('DIV');
+ expect(ReactDOM.findDOMNode(this).tagName).toBe('DIV');
}
return ;
}
diff --git a/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js b/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js
index 40b84fb2dac61..f2ec54f4ea018 100644
--- a/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js
+++ b/packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js
@@ -103,9 +103,9 @@ describe('ReactDeprecationWarnings', () => {
});
}
}
- expect(() => {
- ReactNoop.renderLegacySyncRoot();
- }).toErrorDev([
+
+ ReactNoop.render();
+ await expect(async () => await waitForAll([])).toErrorDev([
'Component "Component" contains the string ref "refComponent". Support for string refs will be removed in a future major release.',
]);
await waitForAll([]);
diff --git a/packages/react-dom/src/__tests__/ReactTestUtils-test.js b/packages/react-dom/src/__tests__/ReactTestUtils-test.js
index e3ed7c71a6883..80871a12d8e52 100644
--- a/packages/react-dom/src/__tests__/ReactTestUtils-test.js
+++ b/packages/react-dom/src/__tests__/ReactTestUtils-test.js
@@ -586,6 +586,7 @@ describe('ReactTestUtils', () => {
});
// @gate !disableDOMTestUtils
+ // @gate !disableLegacyMode
it('should call setState callback with no arguments', async () => {
let mockArgs;
class Component extends React.Component {
diff --git a/packages/react-dom/src/__tests__/findDOMNode-test.js b/packages/react-dom/src/__tests__/findDOMNodeFB-test.js
similarity index 84%
rename from packages/react-dom/src/__tests__/findDOMNode-test.js
rename to packages/react-dom/src/__tests__/findDOMNodeFB-test.js
index 6dfcae82d4cfa..76cb53beba5ec 100644
--- a/packages/react-dom/src/__tests__/findDOMNode-test.js
+++ b/packages/react-dom/src/__tests__/findDOMNodeFB-test.js
@@ -11,16 +11,15 @@
const React = require('react');
const ReactDOM = require('react-dom');
-const findDOMNode =
- ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.findDOMNode;
const StrictMode = React.StrictMode;
describe('findDOMNode', () => {
+ // @gate www && !disableLegacyMode
it('findDOMNode should return null if passed null', () => {
- expect(findDOMNode(null)).toBe(null);
+ expect(ReactDOM.findDOMNode(null)).toBe(null);
});
- // @gate !disableLegacyMode
+ // @gate www && !disableLegacyMode
it('findDOMNode should find dom element', () => {
class MyNode extends React.Component {
render() {
@@ -34,13 +33,13 @@ describe('findDOMNode', () => {
const container = document.createElement('div');
const myNode = ReactDOM.render(, container);
- const myDiv = findDOMNode(myNode);
- const mySameDiv = findDOMNode(myDiv);
+ const myDiv = ReactDOM.findDOMNode(myNode);
+ const mySameDiv = ReactDOM.findDOMNode(myDiv);
expect(myDiv.tagName).toBe('DIV');
expect(mySameDiv).toBe(myDiv);
});
- // @gate !disableLegacyMode
+ // @gate www && !disableLegacyMode
it('findDOMNode should find dom element after an update from null', () => {
function Bar({flag}) {
if (flag) {
@@ -57,23 +56,24 @@ describe('findDOMNode', () => {
const container = document.createElement('div');
const myNodeA = ReactDOM.render(, container);
- const a = findDOMNode(myNodeA);
+ const a = ReactDOM.findDOMNode(myNodeA);
expect(a).toBe(null);
const myNodeB = ReactDOM.render(, container);
expect(myNodeA === myNodeB).toBe(true);
- const b = findDOMNode(myNodeB);
+ const b = ReactDOM.findDOMNode(myNodeB);
expect(b.tagName).toBe('SPAN');
});
+ // @gate www && !disableLegacyMode
it('findDOMNode should reject random objects', () => {
expect(function () {
- findDOMNode({foo: 'bar'});
+ ReactDOM.findDOMNode({foo: 'bar'});
}).toThrowError('Argument appears to not be a ReactComponent. Keys: foo');
});
- // @gate !disableLegacyMode
+ // @gate www && !disableLegacyMode
it('findDOMNode should reject unmounted objects with render func', () => {
class Foo extends React.Component {
render() {
@@ -85,16 +85,16 @@ describe('findDOMNode', () => {
const inst = ReactDOM.render(, container);
ReactDOM.unmountComponentAtNode(container);
- expect(() => findDOMNode(inst)).toThrowError(
+ expect(() => ReactDOM.findDOMNode(inst)).toThrowError(
'Unable to find node on an unmounted component.',
);
});
- // @gate !disableLegacyMode
+ // @gate www && !disableLegacyMode
it('findDOMNode should not throw an error when called within a component that is not mounted', () => {
class Bar extends React.Component {
UNSAFE_componentWillMount() {
- expect(findDOMNode(this)).toBeNull();
+ expect(ReactDOM.findDOMNode(this)).toBeNull();
}
render() {
@@ -107,7 +107,7 @@ describe('findDOMNode', () => {
}).not.toThrow();
});
- // @gate !disableLegacyMode
+ // @gate www && !disableLegacyMode
it('findDOMNode should warn if used to find a host component inside StrictMode', () => {
let parent = undefined;
let child = undefined;
@@ -129,7 +129,7 @@ describe('findDOMNode', () => {
);
let match;
- expect(() => (match = findDOMNode(parent))).toErrorDev([
+ expect(() => (match = ReactDOM.findDOMNode(parent))).toErrorDev([
'Warning: findDOMNode is deprecated in StrictMode. ' +
'findDOMNode was passed an instance of ContainsStrictModeChild which renders StrictMode children. ' +
'Instead, add a ref directly to the element you want to reference. ' +
@@ -141,7 +141,7 @@ describe('findDOMNode', () => {
expect(match).toBe(child);
});
- // @gate !disableLegacyMode
+ // @gate www && !disableLegacyMode
it('findDOMNode should warn if passed a component that is inside StrictMode', () => {
let parent = undefined;
let child = undefined;
@@ -162,7 +162,7 @@ describe('findDOMNode', () => {
);
let match;
- expect(() => (match = findDOMNode(parent))).toErrorDev([
+ expect(() => (match = ReactDOM.findDOMNode(parent))).toErrorDev([
'Warning: findDOMNode is deprecated in StrictMode. ' +
'findDOMNode was passed an instance of IsInStrictMode which is inside StrictMode. ' +
'Instead, add a ref directly to the element you want to reference. ' +
diff --git a/packages/react-dom/src/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js
index 99d2b62f2f845..e0cbe30a079f3 100644
--- a/packages/react-dom/src/client/ReactDOM.js
+++ b/packages/react-dom/src/client/ReactDOM.js
@@ -8,22 +8,12 @@
*/
import type {ReactNodeList} from 'shared/ReactTypes';
-import type {
- Container,
- PublicInstance,
-} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
import type {
RootType,
HydrateRootOptions,
CreateRootOptions,
} from './ReactDOMRoot';
-import {
- findDOMNode,
- render,
- unstable_renderSubtreeIntoContainer,
- unmountComponentAtNode,
-} from './ReactDOMLegacy';
import {
createRoot as createRootImpl,
hydrateRoot as hydrateRootImpl,
@@ -35,6 +25,7 @@ import {
flushSync as flushSyncWithoutWarningIfAlreadyRendering,
isAlreadyRendering,
injectIntoDevTools,
+ findHostInstance,
} from 'react-reconciler/src/ReactFiberReconciler';
import {runWithPriority} from 'react-reconciler/src/ReactEventPriorities';
import {createPortal as createPortalImpl} from 'react-reconciler/src/ReactPortal';
@@ -99,20 +90,6 @@ function createPortal(
return createPortalImpl(children, container, null, key);
}
-function renderSubtreeIntoContainer(
- parentComponent: React$Component,
- element: React$Element,
- containerNode: Container,
- callback: ?Function,
-): React$Component | PublicInstance | null {
- return unstable_renderSubtreeIntoContainer(
- parentComponent,
- element,
- containerNode,
- callback,
- );
-}
-
function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
@@ -163,6 +140,12 @@ function flushSync(fn: (() => R) | void): R | void {
return flushSyncWithoutWarningIfAlreadyRendering(fn);
}
+function findDOMNode(
+ componentOrElement: React$Component,
+): null | Element | Text {
+ return findHostInstance(componentOrElement);
+}
+
// Expose findDOMNode on internals
Internals.findDOMNode = findDOMNode;
@@ -178,15 +161,9 @@ export {
unstable_batchedUpdates,
flushSync,
ReactVersion as version,
- // Disabled behind disableLegacyReactDOMAPIs
- findDOMNode,
- render,
- unmountComponentAtNode,
// exposeConcurrentModeAPIs
createRoot,
hydrateRoot,
- // Disabled behind disableUnstableRenderSubtreeIntoContainer
- renderSubtreeIntoContainer as unstable_renderSubtreeIntoContainer,
// enableCreateEventHandleAPI
createEventHandle as unstable_createEventHandle,
// TODO: Remove this once callers migrate to alternatives.
diff --git a/packages/react-dom/src/client/ReactDOMLegacy.js b/packages/react-dom/src/client/ReactDOMLegacy.js
deleted file mode 100644
index 46220501a5e1e..0000000000000
--- a/packages/react-dom/src/client/ReactDOMLegacy.js
+++ /dev/null
@@ -1,434 +0,0 @@
-/**
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- *
- * @flow
- */
-
-import type {
- Container,
- PublicInstance,
-} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
-import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes';
-import type {ReactNodeList} from 'shared/ReactTypes';
-
-import {disableLegacyMode} from 'shared/ReactFeatureFlags';
-import {clearContainer} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
-import {
- getInstanceFromNode,
- isContainerMarkedAsRoot,
- markContainerAsRoot,
- unmarkContainerAsRoot,
-} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
-import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
-import {isValidContainerLegacy} from './ReactDOMRoot';
-import {
- DOCUMENT_NODE,
- ELEMENT_NODE,
- COMMENT_NODE,
-} from 'react-dom-bindings/src/client/HTMLNodeType';
-
-import {
- createContainer,
- createHydrationContainer,
- findHostInstanceWithNoPortals,
- updateContainer,
- flushSync,
- getPublicRootInstance,
- findHostInstance,
- findHostInstanceWithWarning,
- defaultOnUncaughtError,
- defaultOnCaughtError,
-} from 'react-reconciler/src/ReactFiberReconciler';
-import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
-import getComponentNameFromType from 'shared/getComponentNameFromType';
-import ReactSharedInternals from 'shared/ReactSharedInternals';
-import {has as hasInstance} from 'shared/ReactInstanceMap';
-
-const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
-
-let topLevelUpdateWarnings;
-
-if (__DEV__) {
- topLevelUpdateWarnings = (container: Container) => {
- if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) {
- const hostInstance = findHostInstanceWithNoPortals(
- container._reactRootContainer.current,
- );
- if (hostInstance) {
- if (hostInstance.parentNode !== container) {
- console.error(
- 'It looks like the React-rendered content of this ' +
- 'container was removed without using React. This is not ' +
- 'supported and will cause errors. Instead, call ' +
- 'ReactDOM.unmountComponentAtNode to empty a container.',
- );
- }
- }
- }
-
- const isRootRenderedBySomeReact = !!container._reactRootContainer;
- const rootEl = getReactRootElementInContainer(container);
- const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
-
- if (hasNonRootReactChild && !isRootRenderedBySomeReact) {
- console.error(
- 'Replacing React-rendered children with a new root ' +
- 'component. If you intended to update the children of this node, ' +
- 'you should instead have the existing children update their state ' +
- 'and render the new components instead of calling ReactDOM.render.',
- );
- }
- };
-}
-
-function getReactRootElementInContainer(container: any) {
- if (!container) {
- return null;
- }
-
- if (container.nodeType === DOCUMENT_NODE) {
- return container.documentElement;
- } else {
- return container.firstChild;
- }
-}
-
-function noopOnRecoverableError() {
- // This isn't reachable because onRecoverableError isn't called in the
- // legacy API.
-}
-
-function legacyCreateRootFromDOMContainer(
- container: Container,
- initialChildren: ReactNodeList,
- parentComponent: ?React$Component,
- callback: ?Function,
- isHydrationContainer: boolean,
-): FiberRoot {
- if (isHydrationContainer) {
- if (typeof callback === 'function') {
- const originalCallback = callback;
- callback = function () {
- const instance = getPublicRootInstance(root);
- originalCallback.call(instance);
- };
- }
-
- const root: FiberRoot = createHydrationContainer(
- initialChildren,
- callback,
- container,
- LegacyRoot,
- null, // hydrationCallbacks
- false, // isStrictMode
- false, // concurrentUpdatesByDefaultOverride,
- '', // identifierPrefix
- defaultOnUncaughtError,
- defaultOnCaughtError,
- noopOnRecoverableError,
- // TODO(luna) Support hydration later
- null,
- null,
- );
- container._reactRootContainer = root;
- markContainerAsRoot(root.current, container);
-
- const rootContainerElement =
- container.nodeType === COMMENT_NODE ? container.parentNode : container;
- // $FlowFixMe[incompatible-call]
- listenToAllSupportedEvents(rootContainerElement);
-
- flushSync();
- return root;
- } else {
- // First clear any existing content.
- clearContainer(container);
-
- if (typeof callback === 'function') {
- const originalCallback = callback;
- callback = function () {
- const instance = getPublicRootInstance(root);
- originalCallback.call(instance);
- };
- }
-
- const root = createContainer(
- container,
- LegacyRoot,
- null, // hydrationCallbacks
- false, // isStrictMode
- false, // concurrentUpdatesByDefaultOverride,
- '', // identifierPrefix
- defaultOnUncaughtError,
- defaultOnCaughtError,
- noopOnRecoverableError,
- null, // transitionCallbacks
- );
- container._reactRootContainer = root;
- markContainerAsRoot(root.current, container);
-
- const rootContainerElement =
- container.nodeType === COMMENT_NODE ? container.parentNode : container;
- // $FlowFixMe[incompatible-call]
- listenToAllSupportedEvents(rootContainerElement);
-
- // Initial mount should not be batched.
- flushSync(() => {
- updateContainer(initialChildren, root, parentComponent, callback);
- });
-
- return root;
- }
-}
-
-function warnOnInvalidCallback(callback: mixed): void {
- if (__DEV__) {
- if (callback !== null && typeof callback !== 'function') {
- console.error(
- 'Expected the last optional `callback` argument to be a ' +
- 'function. Instead received: %s.',
- callback,
- );
- }
- }
-}
-
-function legacyRenderSubtreeIntoContainer(
- parentComponent: ?React$Component,
- children: ReactNodeList,
- container: Container,
- forceHydrate: boolean,
- callback: ?Function,
-): React$Component | PublicInstance | null {
- if (__DEV__) {
- topLevelUpdateWarnings(container);
- warnOnInvalidCallback(callback === undefined ? null : callback);
- }
-
- const maybeRoot = container._reactRootContainer;
- let root: FiberRoot;
- if (!maybeRoot) {
- // Initial mount
- root = legacyCreateRootFromDOMContainer(
- container,
- children,
- parentComponent,
- callback,
- forceHydrate,
- );
- } else {
- root = maybeRoot;
- if (typeof callback === 'function') {
- const originalCallback = callback;
- callback = function () {
- const instance = getPublicRootInstance(root);
- originalCallback.call(instance);
- };
- }
- // Update
- updateContainer(children, root, parentComponent, callback);
- }
- return getPublicRootInstance(root);
-}
-
-export type FindDOMNodeType = typeof findDOMNode;
-
-export function findDOMNode(
- componentOrElement: Element | ?React$Component,
-): null | Element | Text {
- if (__DEV__) {
- const owner = (ReactCurrentOwner.current: any);
- if (owner !== null && owner.stateNode !== null) {
- const warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender;
- if (!warnedAboutRefsInRender) {
- console.error(
- '%s is accessing findDOMNode inside its render(). ' +
- 'render() should be a pure function of props and state. It should ' +
- 'never access something that requires stale data from the previous ' +
- 'render, such as refs. Move this logic to componentDidMount and ' +
- 'componentDidUpdate instead.',
- getComponentNameFromType(owner.type) || 'A component',
- );
- }
- owner.stateNode._warnedAboutRefsInRender = true;
- }
- }
- if (componentOrElement == null) {
- return null;
- }
- if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
- return (componentOrElement: any);
- }
- if (__DEV__) {
- return findHostInstanceWithWarning(componentOrElement, 'findDOMNode');
- }
- return findHostInstance(componentOrElement);
-}
-
-export function render(
- element: React$Element,
- container: Container,
- callback: ?Function,
-): React$Component | PublicInstance | null {
- if (disableLegacyMode) {
- if (__DEV__) {
- console.error(
- 'ReactDOM.render was removed in React 19. Use createRoot instead.',
- );
- }
- throw new Error('ReactDOM: Unsupported Legacy Mode API.');
- }
- if (__DEV__) {
- console.error(
- 'ReactDOM.render has not been supported since React 18. Use createRoot ' +
- 'instead. Until you switch to the new API, your app will behave as ' +
- "if it's running React 17. Learn " +
- 'more: https://react.dev/link/switch-to-createroot',
- );
- }
-
- if (!isValidContainerLegacy(container)) {
- throw new Error('Target container is not a DOM element.');
- }
-
- if (__DEV__) {
- const isModernRoot =
- isContainerMarkedAsRoot(container) &&
- container._reactRootContainer === undefined;
- if (isModernRoot) {
- console.error(
- 'You are calling ReactDOM.render() on a container that was previously ' +
- 'passed to ReactDOMClient.createRoot(). This is not supported. ' +
- 'Did you mean to call root.render(element)?',
- );
- }
- }
- return legacyRenderSubtreeIntoContainer(
- null,
- element,
- container,
- false,
- callback,
- );
-}
-
-export function unstable_renderSubtreeIntoContainer(
- parentComponent: React$Component,
- element: React$Element,
- containerNode: Container,
- callback: ?Function,
-): React$Component | PublicInstance | null {
- if (disableLegacyMode) {
- if (__DEV__) {
- console.error(
- 'ReactDOM.unstable_renderSubtreeIntoContainer() was removed in React 19. Consider using a portal instead.',
- );
- }
- throw new Error('ReactDOM: Unsupported Legacy Mode API.');
- }
- if (__DEV__) {
- console.error(
- 'ReactDOM.unstable_renderSubtreeIntoContainer() has not been supported ' +
- 'since React 18. Consider using a portal instead. Until you switch to ' +
- "the createRoot API, your app will behave as if it's running React " +
- '17. Learn more: https://react.dev/link/switch-to-createroot',
- );
- }
-
- if (!isValidContainerLegacy(containerNode)) {
- throw new Error('Target container is not a DOM element.');
- }
-
- if (parentComponent == null || !hasInstance(parentComponent)) {
- throw new Error('parentComponent must be a valid React Component');
- }
-
- return legacyRenderSubtreeIntoContainer(
- parentComponent,
- element,
- containerNode,
- false,
- callback,
- );
-}
-
-export function unmountComponentAtNode(container: Container): boolean {
- if (disableLegacyMode) {
- if (__DEV__) {
- console.error(
- 'unmountComponentAtNode was removed in React 19. Use root.unmount() instead.',
- );
- }
- throw new Error('ReactDOM: Unsupported Legacy Mode API.');
- }
- if (!isValidContainerLegacy(container)) {
- throw new Error('Target container is not a DOM element.');
- }
-
- if (__DEV__) {
- const isModernRoot =
- isContainerMarkedAsRoot(container) &&
- container._reactRootContainer === undefined;
- if (isModernRoot) {
- console.error(
- 'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
- 'passed to ReactDOMClient.createRoot(). This is not supported. Did you mean to call root.unmount()?',
- );
- }
- }
-
- if (container._reactRootContainer) {
- if (__DEV__) {
- const rootEl = getReactRootElementInContainer(container);
- const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
- if (renderedByDifferentReact) {
- console.error(
- "unmountComponentAtNode(): The node you're attempting to unmount " +
- 'was rendered by another copy of React.',
- );
- }
- }
-
- // Unmount should not be batched.
- flushSync(() => {
- legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
- // $FlowFixMe[incompatible-type] This should probably use `delete container._reactRootContainer`
- container._reactRootContainer = null;
- unmarkContainerAsRoot(container);
- });
- });
- // If you call unmountComponentAtNode twice in quick succession, you'll
- // get `true` twice. That's probably fine?
- return true;
- } else {
- if (__DEV__) {
- const rootEl = getReactRootElementInContainer(container);
- const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
-
- // Check if the container itself is a React root node.
- const isContainerReactRoot =
- container.nodeType === ELEMENT_NODE &&
- isValidContainerLegacy(container.parentNode) &&
- // $FlowFixMe[prop-missing]
- // $FlowFixMe[incompatible-use]
- !!container.parentNode._reactRootContainer;
-
- if (hasNonRootReactChild) {
- console.error(
- "unmountComponentAtNode(): The node you're attempting to unmount " +
- 'was rendered by React and is not a top-level container. %s',
- isContainerReactRoot
- ? 'You may have accidentally passed in a React root node instead ' +
- 'of its container.'
- : 'Instead, have the parent component update its state and ' +
- 'rerender in order to remove this component.',
- );
- }
- }
-
- return false;
- }
-}
diff --git a/packages/react-dom/src/client/ReactDOMRootFB.js b/packages/react-dom/src/client/ReactDOMRootFB.js
index 138f781e6caa7..4bf5b43f6e51d 100644
--- a/packages/react-dom/src/client/ReactDOMRootFB.js
+++ b/packages/react-dom/src/client/ReactDOMRootFB.js
@@ -33,11 +33,13 @@ import {
getInstanceFromNode,
isContainerMarkedAsRoot,
markContainerAsRoot,
+ unmarkContainerAsRoot,
} from 'react-dom-bindings/src/client/ReactDOMComponentTree';
import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
import {isValidContainerLegacy} from './ReactDOMRoot';
import {
DOCUMENT_NODE,
+ ELEMENT_NODE,
COMMENT_NODE,
} from 'react-dom-bindings/src/client/HTMLNodeType';
@@ -49,12 +51,17 @@ import {
updateContainer,
flushSync,
getPublicRootInstance,
+ findHostInstance,
+ findHostInstanceWithWarning,
defaultOnUncaughtError,
defaultOnCaughtError,
} from 'react-reconciler/src/ReactFiberReconciler';
import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
+import getComponentNameFromType from 'shared/getComponentNameFromType';
import {has as hasInstance} from 'shared/ReactInstanceMap';
+import ReactSharedInternals from 'shared/ReactSharedInternals';
+
import assign from 'shared/assign';
// Provided by www
@@ -146,6 +153,8 @@ export function hydrateRoot(
);
}
+const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
+
let topLevelUpdateWarnings;
if (__DEV__) {
@@ -331,6 +340,38 @@ function legacyRenderSubtreeIntoContainer(
return getPublicRootInstance(root);
}
+export function findDOMNode(
+ componentOrElement: Element | ?React$Component,
+): null | Element | Text {
+ if (__DEV__) {
+ const owner = (ReactCurrentOwner.current: any);
+ if (owner !== null && owner.stateNode !== null) {
+ const warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender;
+ if (!warnedAboutRefsInRender) {
+ console.error(
+ '%s is accessing findDOMNode inside its render(). ' +
+ 'render() should be a pure function of props and state. It should ' +
+ 'never access something that requires stale data from the previous ' +
+ 'render, such as refs. Move this logic to componentDidMount and ' +
+ 'componentDidUpdate instead.',
+ getComponentNameFromType(owner.type) || 'A component',
+ );
+ }
+ owner.stateNode._warnedAboutRefsInRender = true;
+ }
+ }
+ if (componentOrElement == null) {
+ return null;
+ }
+ if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
+ return (componentOrElement: any);
+ }
+ if (__DEV__) {
+ return findHostInstanceWithWarning(componentOrElement, 'findDOMNode');
+ }
+ return findHostInstance(componentOrElement);
+}
+
export function render(
element: React$Element,
container: Container,
@@ -418,4 +459,82 @@ export function unstable_renderSubtreeIntoContainer(
);
}
+export function unmountComponentAtNode(container: Container): boolean {
+ if (disableLegacyMode) {
+ if (__DEV__) {
+ console.error(
+ 'unmountComponentAtNode was removed in React 19. Use root.unmount() instead.',
+ );
+ }
+ throw new Error('ReactDOM: Unsupported Legacy Mode API.');
+ }
+ if (!isValidContainerLegacy(container)) {
+ throw new Error('Target container is not a DOM element.');
+ }
+
+ if (__DEV__) {
+ const isModernRoot =
+ isContainerMarkedAsRoot(container) &&
+ container._reactRootContainer === undefined;
+ if (isModernRoot) {
+ console.error(
+ 'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
+ 'passed to ReactDOMClient.createRoot(). This is not supported. Did you mean to call root.unmount()?',
+ );
+ }
+ }
+
+ if (container._reactRootContainer) {
+ if (__DEV__) {
+ const rootEl = getReactRootElementInContainer(container);
+ const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
+ if (renderedByDifferentReact) {
+ console.error(
+ "unmountComponentAtNode(): The node you're attempting to unmount " +
+ 'was rendered by another copy of React.',
+ );
+ }
+ }
+
+ // Unmount should not be batched.
+ flushSync(() => {
+ legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
+ // $FlowFixMe[incompatible-type] This should probably use `delete container._reactRootContainer`
+ container._reactRootContainer = null;
+ unmarkContainerAsRoot(container);
+ });
+ });
+ // If you call unmountComponentAtNode twice in quick succession, you'll
+ // get `true` twice. That's probably fine?
+ return true;
+ } else {
+ if (__DEV__) {
+ const rootEl = getReactRootElementInContainer(container);
+ const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
+
+ // Check if the container itself is a React root node.
+ const isContainerReactRoot =
+ container.nodeType === ELEMENT_NODE &&
+ isValidContainerLegacy(container.parentNode) &&
+ // $FlowFixMe[prop-missing]
+ // $FlowFixMe[incompatible-use]
+ !!container.parentNode._reactRootContainer;
+
+ if (hasNonRootReactChild) {
+ console.error(
+ "unmountComponentAtNode(): The node you're attempting to unmount " +
+ 'was rendered by React and is not a top-level container. %s',
+ isContainerReactRoot
+ ? 'You may have accidentally passed in a React root node instead ' +
+ 'of its container.'
+ : 'Instead, have the parent component update its state and ' +
+ 'rerender in order to remove this component.',
+ );
+ }
+ }
+
+ return false;
+ }
+}
+
export {batchedUpdates as unstable_batchedUpdates};
diff --git a/packages/react-dom/unstable_testing.js b/packages/react-dom/unstable_testing.js
index 1e973748c7cb8..19cc1515cdffe 100644
--- a/packages/react-dom/unstable_testing.js
+++ b/packages/react-dom/unstable_testing.js
@@ -10,11 +10,8 @@
export {
createPortal,
flushSync,
- render,
- unmountComponentAtNode,
unstable_batchedUpdates,
unstable_createEventHandle,
- unstable_renderSubtreeIntoContainer,
unstable_runWithPriority, // DO NOT USE: Temporarily exposed to migrate off of Scheduler.runWithPriority.
useFormStatus,
useFormState,
diff --git a/packages/react-dom/unstable_testing.stable.js b/packages/react-dom/unstable_testing.stable.js
index 0b47d34c0acb0..a0a460ce83a99 100644
--- a/packages/react-dom/unstable_testing.stable.js
+++ b/packages/react-dom/unstable_testing.stable.js
@@ -10,10 +10,7 @@
export {
createPortal,
flushSync,
- render,
- unmountComponentAtNode,
unstable_batchedUpdates,
- unstable_renderSubtreeIntoContainer,
useFormStatus,
useFormState,
prefetchDNS,
diff --git a/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js b/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
index 4ad7bab5614bb..fd2982b81141f 100644
--- a/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactScope-test.internal.js
@@ -393,7 +393,7 @@ describe('ReactScope', () => {
});
// @gate www
- it('DO_NOT_USE_queryAllNodes() works as intended', () => {
+ it('DO_NOT_USE_queryAllNodes() works as intended', async () => {
const testScopeQuery = (type, props) => true;
const TestScope = React.unstable_Scope;
const scopeRef = React.createRef();
@@ -417,20 +417,25 @@ describe('ReactScope', () => {
);
}
- const renderer = ReactTestRenderer.create(, {
- createNodeMock: element => {
- return element;
- },
- });
+ let renderer;
+ await act(
+ () =>
+ (renderer = ReactTestRenderer.create(, {
+ createNodeMock: element => {
+ return element;
+ },
+ unstable_isConcurrent: true,
+ })),
+ );
let nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
expect(nodes).toEqual([divRef.current, spanRef.current, aRef.current]);
- renderer.update();
+ await act(() => renderer.update());
nodes = scopeRef.current.DO_NOT_USE_queryAllNodes(testScopeQuery);
expect(nodes).toEqual([aRef.current, divRef.current, spanRef.current]);
});
// @gate www
- it('DO_NOT_USE_queryFirstNode() works as intended', () => {
+ it('DO_NOT_USE_queryFirstNode() works as intended', async () => {
const testScopeQuery = (type, props) => true;
const TestScope = React.unstable_Scope;
const scopeRef = React.createRef();
@@ -454,20 +459,26 @@ describe('ReactScope', () => {
);
}
- const renderer = ReactTestRenderer.create(, {
- createNodeMock: element => {
- return element;
- },
- });
+ let renderer;
+ await act(
+ () =>
+ (renderer = ReactTestRenderer.create(, {
+ createNodeMock: element => {
+ return element;
+ },
+ unstable_isConcurrent: true,
+ })),
+ );
let node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
expect(node).toEqual(divRef.current);
- renderer.update();
+ await act(() => renderer.update());
+
node = scopeRef.current.DO_NOT_USE_queryFirstNode(testScopeQuery);
expect(node).toEqual(aRef.current);
});
// @gate www
- it('containsNode() works as intended', () => {
+ it('containsNode() works as intended', async () => {
const TestScope = React.unstable_Scope;
const scopeRef = React.createRef();
const divRef = React.createRef();
@@ -500,23 +511,28 @@ describe('ReactScope', () => {
);
}
- const renderer = ReactTestRenderer.create(, {
- createNodeMock: element => {
- return element;
- },
- });
+ let renderer;
+ await act(
+ () =>
+ (renderer = ReactTestRenderer.create(, {
+ createNodeMock: element => {
+ return element;
+ },
+ unstable_isConcurrent: true,
+ })),
+ );
expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
- renderer.update();
+ await act(() => renderer.update());
expect(scopeRef.current.containsNode(divRef.current)).toBe(true);
expect(scopeRef.current.containsNode(spanRef.current)).toBe(true);
expect(scopeRef.current.containsNode(aRef.current)).toBe(true);
expect(scopeRef.current.containsNode(outerSpan.current)).toBe(false);
expect(scopeRef.current.containsNode(emRef.current)).toBe(true);
- renderer.update();
+ await act(() => renderer.update());
expect(scopeRef.current.containsNode(emRef.current)).toBe(false);
});
});
diff --git a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
index 14c58cb0d6581..8e1ba771a7dd5 100644
--- a/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
+++ b/packages/react-reconciler/src/__tests__/ReactSuspense-test.internal.js
@@ -378,7 +378,7 @@ describe('ReactSuspense', () => {
expect(container.textContent).toEqual('AB');
});
- // @gate forceConcurrentByDefaultForTesting
+ // @gate !disableLegacyMode && forceConcurrentByDefaultForTesting
it(
'interrupts current render when something suspends with a ' +
"delay and we've already skipped over a lower priority update in " +
diff --git a/packages/react/src/__tests__/ReactJSXRuntime-test.js b/packages/react/src/__tests__/ReactJSXRuntime-test.js
index db488e1684001..1c57954b205c4 100644
--- a/packages/react/src/__tests__/ReactJSXRuntime-test.js
+++ b/packages/react/src/__tests__/ReactJSXRuntime-test.js
@@ -10,12 +10,10 @@
'use strict';
let React;
-let ReactDOM;
let ReactDOMClient;
let JSXRuntime;
let JSXDEVRuntime;
let act;
-let findDOMNode;
// NOTE: Prefer to call the JSXRuntime directly in these tests so we can be
// certain that we are testing the runtime behavior, as opposed to the Babel
@@ -27,11 +25,8 @@ describe('ReactJSXRuntime', () => {
React = require('react');
JSXRuntime = require('react/jsx-runtime');
JSXDEVRuntime = require('react/jsx-dev-runtime');
- ReactDOM = require('react-dom');
ReactDOMClient = require('react-dom/client');
act = require('internal-test-utils').act;
- findDOMNode =
- ReactDOM.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.findDOMNode;
});
it('allows static methods to be called using the type property', () => {
@@ -133,9 +128,9 @@ describe('ReactJSXRuntime', () => {
const outer = container.firstChild;
if (__DEV__) {
- expect(findDOMNode(outer).className).toBe('moo');
+ expect(outer.className).toBe('moo');
} else {
- expect(findDOMNode(outer).className).toBe('quack');
+ expect(outer.className).toBe('quack');
}
});
diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js
index 7113757b66676..e9f25eea1dc69 100644
--- a/packages/shared/ReactFeatureFlags.js
+++ b/packages/shared/ReactFeatureFlags.js
@@ -178,7 +178,7 @@ export const enableReactTestRendererWarning = __NEXT_MAJOR__;
// Disables legacy mode
// This allows us to land breaking changes to remove legacy mode APIs in experimental builds
// before removing them in stable in the next Major
-export const disableLegacyMode = __NEXT_MAJOR__;
+export const disableLegacyMode = true;
export const disableDOMTestUtils = __NEXT_MAJOR__;
diff --git a/packages/shared/forks/ReactFeatureFlags.test-renderer.js b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
index 67ef3589ca487..dd5f9299e1536 100644
--- a/packages/shared/forks/ReactFeatureFlags.test-renderer.js
+++ b/packages/shared/forks/ReactFeatureFlags.test-renderer.js
@@ -88,7 +88,7 @@ const __NEXT_MAJOR__ = __EXPERIMENTAL__;
export const enableRefAsProp = __NEXT_MAJOR__;
export const disableStringRefs = __NEXT_MAJOR__;
export const enableBigIntSupport = __NEXT_MAJOR__;
-export const disableLegacyMode = __NEXT_MAJOR__;
+export const disableLegacyMode = true;
export const disableLegacyContext = __NEXT_MAJOR__;
export const disableDOMTestUtils = __NEXT_MAJOR__;
export const enableRenderableContext = __NEXT_MAJOR__;
diff --git a/packages/shared/forks/ReactFeatureFlags.www.js b/packages/shared/forks/ReactFeatureFlags.www.js
index a1b5dd8dd950a..ef7a207974cf8 100644
--- a/packages/shared/forks/ReactFeatureFlags.www.js
+++ b/packages/shared/forks/ReactFeatureFlags.www.js
@@ -113,7 +113,7 @@ export const useModernStrictMode = true;
// because JSX is an extremely hot path.
export const disableStringRefs = false;
-export const disableLegacyMode = false;
+export const disableLegacyMode = __EXPERIMENTAL__;
export const disableDOMTestUtils = false;