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.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/client/ReactDOM.js b/packages/react-dom/src/client/ReactDOM.js index 99d2b62f2f845..2cfb33bdf4685 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: Element | ?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/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__;