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 {clearContainer} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
    
  18. import {
    
  19.   getInstanceFromNode,
    
  20.   isContainerMarkedAsRoot,
    
  21.   markContainerAsRoot,
    
  22.   unmarkContainerAsRoot,
    
  23. } from 'react-dom-bindings/src/client/ReactDOMComponentTree';
    
  24. import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
    
  25. import {isValidContainerLegacy} from './ReactDOMRoot';
    
  26. import {
    
  27.   DOCUMENT_NODE,
    
  28.   ELEMENT_NODE,
    
  29.   COMMENT_NODE,
    
  30. } from 'react-dom-bindings/src/client/HTMLNodeType';
    
  31. 
    
  32. import {
    
  33.   createContainer,
    
  34.   createHydrationContainer,
    
  35.   findHostInstanceWithNoPortals,
    
  36.   updateContainer,
    
  37.   flushSync,
    
  38.   getPublicRootInstance,
    
  39.   findHostInstance,
    
  40.   findHostInstanceWithWarning,
    
  41. } from 'react-reconciler/src/ReactFiberReconciler';
    
  42. import {LegacyRoot} from 'react-reconciler/src/ReactRootTags';
    
  43. import getComponentNameFromType from 'shared/getComponentNameFromType';
    
  44. import ReactSharedInternals from 'shared/ReactSharedInternals';
    
  45. import {has as hasInstance} from 'shared/ReactInstanceMap';
    
  46. import {enableHostSingletons} from '../../../shared/ReactFeatureFlags';
    
  47. 
    
  48. const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
    
  49. 
    
  50. let topLevelUpdateWarnings;
    
  51. 
    
  52. if (__DEV__) {
    
  53.   topLevelUpdateWarnings = (container: Container) => {
    
  54.     if (container._reactRootContainer && container.nodeType !== COMMENT_NODE) {
    
  55.       const hostInstance = findHostInstanceWithNoPortals(
    
  56.         container._reactRootContainer.current,
    
  57.       );
    
  58.       if (hostInstance) {
    
  59.         if (hostInstance.parentNode !== container) {
    
  60.           console.error(
    
  61.             'render(...): It looks like the React-rendered content of this ' +
    
  62.               'container was removed without using React. This is not ' +
    
  63.               'supported and will cause errors. Instead, call ' +
    
  64.               'ReactDOM.unmountComponentAtNode to empty a container.',
    
  65.           );
    
  66.         }
    
  67.       }
    
  68.     }
    
  69. 
    
  70.     const isRootRenderedBySomeReact = !!container._reactRootContainer;
    
  71.     const rootEl = getReactRootElementInContainer(container);
    
  72.     const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
    
  73. 
    
  74.     if (hasNonRootReactChild && !isRootRenderedBySomeReact) {
    
  75.       console.error(
    
  76.         'render(...): Replacing React-rendered children with a new root ' +
    
  77.           'component. If you intended to update the children of this node, ' +
    
  78.           'you should instead have the existing children update their state ' +
    
  79.           'and render the new components instead of calling ReactDOM.render.',
    
  80.       );
    
  81.     }
    
  82. 
    
  83.     if (
    
  84.       !enableHostSingletons &&
    
  85.       container.nodeType === ELEMENT_NODE &&
    
  86.       ((container: any): Element).tagName &&
    
  87.       ((container: any): Element).tagName.toUpperCase() === 'BODY'
    
  88.     ) {
    
  89.       console.error(
    
  90.         'render(): Rendering components directly into document.body is ' +
    
  91.           'discouraged, since its children are often manipulated by third-party ' +
    
  92.           'scripts and browser extensions. This may lead to subtle ' +
    
  93.           'reconciliation issues. Try rendering into a container element created ' +
    
  94.           'for your app.',
    
  95.       );
    
  96.     }
    
  97.   };
    
  98. }
    
  99. 
    
  100. function getReactRootElementInContainer(container: any) {
    
  101.   if (!container) {
    
  102.     return null;
    
  103.   }
    
  104. 
    
  105.   if (container.nodeType === DOCUMENT_NODE) {
    
  106.     return container.documentElement;
    
  107.   } else {
    
  108.     return container.firstChild;
    
  109.   }
    
  110. }
    
  111. 
    
  112. function noopOnRecoverableError() {
    
  113.   // This isn't reachable because onRecoverableError isn't called in the
    
  114.   // legacy API.
    
  115. }
    
  116. 
    
  117. function legacyCreateRootFromDOMContainer(
    
  118.   container: Container,
    
  119.   initialChildren: ReactNodeList,
    
  120.   parentComponent: ?React$Component<any, any>,
    
  121.   callback: ?Function,
    
  122.   isHydrationContainer: boolean,
    
  123. ): FiberRoot {
    
  124.   if (isHydrationContainer) {
    
  125.     if (typeof callback === 'function') {
    
  126.       const originalCallback = callback;
    
  127.       callback = function () {
    
  128.         const instance = getPublicRootInstance(root);
    
  129.         originalCallback.call(instance);
    
  130.       };
    
  131.     }
    
  132. 
    
  133.     const root: FiberRoot = createHydrationContainer(
    
  134.       initialChildren,
    
  135.       callback,
    
  136.       container,
    
  137.       LegacyRoot,
    
  138.       null, // hydrationCallbacks
    
  139.       false, // isStrictMode
    
  140.       false, // concurrentUpdatesByDefaultOverride,
    
  141.       '', // identifierPrefix
    
  142.       noopOnRecoverableError,
    
  143.       // TODO(luna) Support hydration later
    
  144.       null,
    
  145.       null,
    
  146.     );
    
  147.     container._reactRootContainer = root;
    
  148.     markContainerAsRoot(root.current, container);
    
  149. 
    
  150.     const rootContainerElement =
    
  151.       container.nodeType === COMMENT_NODE ? container.parentNode : container;
    
  152.     // $FlowFixMe[incompatible-call]
    
  153.     listenToAllSupportedEvents(rootContainerElement);
    
  154. 
    
  155.     flushSync();
    
  156.     return root;
    
  157.   } else {
    
  158.     // First clear any existing content.
    
  159.     clearContainer(container);
    
  160. 
    
  161.     if (typeof callback === 'function') {
    
  162.       const originalCallback = callback;
    
  163.       callback = function () {
    
  164.         const instance = getPublicRootInstance(root);
    
  165.         originalCallback.call(instance);
    
  166.       };
    
  167.     }
    
  168. 
    
  169.     const root = createContainer(
    
  170.       container,
    
  171.       LegacyRoot,
    
  172.       null, // hydrationCallbacks
    
  173.       false, // isStrictMode
    
  174.       false, // concurrentUpdatesByDefaultOverride,
    
  175.       '', // identifierPrefix
    
  176.       noopOnRecoverableError, // onRecoverableError
    
  177.       null, // transitionCallbacks
    
  178.     );
    
  179.     container._reactRootContainer = root;
    
  180.     markContainerAsRoot(root.current, container);
    
  181. 
    
  182.     const rootContainerElement =
    
  183.       container.nodeType === COMMENT_NODE ? container.parentNode : container;
    
  184.     // $FlowFixMe[incompatible-call]
    
  185.     listenToAllSupportedEvents(rootContainerElement);
    
  186. 
    
  187.     // Initial mount should not be batched.
    
  188.     flushSync(() => {
    
  189.       updateContainer(initialChildren, root, parentComponent, callback);
    
  190.     });
    
  191. 
    
  192.     return root;
    
  193.   }
    
  194. }
    
  195. 
    
  196. function warnOnInvalidCallback(callback: mixed, callerName: string): void {
    
  197.   if (__DEV__) {
    
  198.     if (callback !== null && typeof callback !== 'function') {
    
  199.       console.error(
    
  200.         '%s(...): Expected the last optional `callback` argument to be a ' +
    
  201.           'function. Instead received: %s.',
    
  202.         callerName,
    
  203.         callback,
    
  204.       );
    
  205.     }
    
  206.   }
    
  207. }
    
  208. 
    
  209. function legacyRenderSubtreeIntoContainer(
    
  210.   parentComponent: ?React$Component<any, any>,
    
  211.   children: ReactNodeList,
    
  212.   container: Container,
    
  213.   forceHydrate: boolean,
    
  214.   callback: ?Function,
    
  215. ): React$Component<any, any> | PublicInstance | null {
    
  216.   if (__DEV__) {
    
  217.     topLevelUpdateWarnings(container);
    
  218.     warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
    
  219.   }
    
  220. 
    
  221.   const maybeRoot = container._reactRootContainer;
    
  222.   let root: FiberRoot;
    
  223.   if (!maybeRoot) {
    
  224.     // Initial mount
    
  225.     root = legacyCreateRootFromDOMContainer(
    
  226.       container,
    
  227.       children,
    
  228.       parentComponent,
    
  229.       callback,
    
  230.       forceHydrate,
    
  231.     );
    
  232.   } else {
    
  233.     root = maybeRoot;
    
  234.     if (typeof callback === 'function') {
    
  235.       const originalCallback = callback;
    
  236.       callback = function () {
    
  237.         const instance = getPublicRootInstance(root);
    
  238.         originalCallback.call(instance);
    
  239.       };
    
  240.     }
    
  241.     // Update
    
  242.     updateContainer(children, root, parentComponent, callback);
    
  243.   }
    
  244.   return getPublicRootInstance(root);
    
  245. }
    
  246. 
    
  247. export function findDOMNode(
    
  248.   componentOrElement: Element | ?React$Component<any, any>,
    
  249. ): null | Element | Text {
    
  250.   if (__DEV__) {
    
  251.     const owner = (ReactCurrentOwner.current: any);
    
  252.     if (owner !== null && owner.stateNode !== null) {
    
  253.       const warnedAboutRefsInRender = owner.stateNode._warnedAboutRefsInRender;
    
  254.       if (!warnedAboutRefsInRender) {
    
  255.         console.error(
    
  256.           '%s is accessing findDOMNode inside its render(). ' +
    
  257.             'render() should be a pure function of props and state. It should ' +
    
  258.             'never access something that requires stale data from the previous ' +
    
  259.             'render, such as refs. Move this logic to componentDidMount and ' +
    
  260.             'componentDidUpdate instead.',
    
  261.           getComponentNameFromType(owner.type) || 'A component',
    
  262.         );
    
  263.       }
    
  264.       owner.stateNode._warnedAboutRefsInRender = true;
    
  265.     }
    
  266.   }
    
  267.   if (componentOrElement == null) {
    
  268.     return null;
    
  269.   }
    
  270.   if ((componentOrElement: any).nodeType === ELEMENT_NODE) {
    
  271.     return (componentOrElement: any);
    
  272.   }
    
  273.   if (__DEV__) {
    
  274.     return findHostInstanceWithWarning(componentOrElement, 'findDOMNode');
    
  275.   }
    
  276.   return findHostInstance(componentOrElement);
    
  277. }
    
  278. 
    
  279. export function hydrate(
    
  280.   element: React$Node,
    
  281.   container: Container,
    
  282.   callback: ?Function,
    
  283. ): React$Component<any, any> | PublicInstance | null {
    
  284.   if (__DEV__) {
    
  285.     console.error(
    
  286.       'ReactDOM.hydrate is no longer supported in React 18. Use hydrateRoot ' +
    
  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://reactjs.org/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.hydrate() on a container that was previously ' +
    
  304.           'passed to ReactDOMClient.createRoot(). This is not supported. ' +
    
  305.           'Did you mean to call hydrateRoot(container, element)?',
    
  306.       );
    
  307.     }
    
  308.   }
    
  309.   // TODO: throw or warn if we couldn't hydrate?
    
  310.   return legacyRenderSubtreeIntoContainer(
    
  311.     null,
    
  312.     element,
    
  313.     container,
    
  314.     true,
    
  315.     callback,
    
  316.   );
    
  317. }
    
  318. 
    
  319. export function render(
    
  320.   element: React$Element<any>,
    
  321.   container: Container,
    
  322.   callback: ?Function,
    
  323. ): React$Component<any, any> | PublicInstance | null {
    
  324.   if (__DEV__) {
    
  325.     console.error(
    
  326.       'ReactDOM.render is no longer supported in React 18. Use createRoot ' +
    
  327.         'instead. Until you switch to the new API, your app will behave as ' +
    
  328.         "if it's running React 17. Learn " +
    
  329.         'more: https://reactjs.org/link/switch-to-createroot',
    
  330.     );
    
  331.   }
    
  332. 
    
  333.   if (!isValidContainerLegacy(container)) {
    
  334.     throw new Error('Target container is not a DOM element.');
    
  335.   }
    
  336. 
    
  337.   if (__DEV__) {
    
  338.     const isModernRoot =
    
  339.       isContainerMarkedAsRoot(container) &&
    
  340.       container._reactRootContainer === undefined;
    
  341.     if (isModernRoot) {
    
  342.       console.error(
    
  343.         'You are calling ReactDOM.render() on a container that was previously ' +
    
  344.           'passed to ReactDOMClient.createRoot(). This is not supported. ' +
    
  345.           'Did you mean to call root.render(element)?',
    
  346.       );
    
  347.     }
    
  348.   }
    
  349.   return legacyRenderSubtreeIntoContainer(
    
  350.     null,
    
  351.     element,
    
  352.     container,
    
  353.     false,
    
  354.     callback,
    
  355.   );
    
  356. }
    
  357. 
    
  358. export function unstable_renderSubtreeIntoContainer(
    
  359.   parentComponent: React$Component<any, any>,
    
  360.   element: React$Element<any>,
    
  361.   containerNode: Container,
    
  362.   callback: ?Function,
    
  363. ): React$Component<any, any> | PublicInstance | null {
    
  364.   if (__DEV__) {
    
  365.     console.error(
    
  366.       'ReactDOM.unstable_renderSubtreeIntoContainer() is no longer supported ' +
    
  367.         'in React 18. Consider using a portal instead. Until you switch to ' +
    
  368.         "the createRoot API, your app will behave as if it's running React " +
    
  369.         '17. Learn more: https://reactjs.org/link/switch-to-createroot',
    
  370.     );
    
  371.   }
    
  372. 
    
  373.   if (!isValidContainerLegacy(containerNode)) {
    
  374.     throw new Error('Target container is not a DOM element.');
    
  375.   }
    
  376. 
    
  377.   if (parentComponent == null || !hasInstance(parentComponent)) {
    
  378.     throw new Error('parentComponent must be a valid React Component');
    
  379.   }
    
  380. 
    
  381.   return legacyRenderSubtreeIntoContainer(
    
  382.     parentComponent,
    
  383.     element,
    
  384.     containerNode,
    
  385.     false,
    
  386.     callback,
    
  387.   );
    
  388. }
    
  389. 
    
  390. export function unmountComponentAtNode(container: Container): boolean {
    
  391.   if (!isValidContainerLegacy(container)) {
    
  392.     throw new Error(
    
  393.       'unmountComponentAtNode(...): Target container is not a DOM element.',
    
  394.     );
    
  395.   }
    
  396. 
    
  397.   if (__DEV__) {
    
  398.     const isModernRoot =
    
  399.       isContainerMarkedAsRoot(container) &&
    
  400.       container._reactRootContainer === undefined;
    
  401.     if (isModernRoot) {
    
  402.       console.error(
    
  403.         'You are calling ReactDOM.unmountComponentAtNode() on a container that was previously ' +
    
  404.           'passed to ReactDOMClient.createRoot(). This is not supported. Did you mean to call root.unmount()?',
    
  405.       );
    
  406.     }
    
  407.   }
    
  408. 
    
  409.   if (container._reactRootContainer) {
    
  410.     if (__DEV__) {
    
  411.       const rootEl = getReactRootElementInContainer(container);
    
  412.       const renderedByDifferentReact = rootEl && !getInstanceFromNode(rootEl);
    
  413.       if (renderedByDifferentReact) {
    
  414.         console.error(
    
  415.           "unmountComponentAtNode(): The node you're attempting to unmount " +
    
  416.             'was rendered by another copy of React.',
    
  417.         );
    
  418.       }
    
  419.     }
    
  420. 
    
  421.     // Unmount should not be batched.
    
  422.     flushSync(() => {
    
  423.       legacyRenderSubtreeIntoContainer(null, null, container, false, () => {
    
  424.         // $FlowFixMe[incompatible-type] This should probably use `delete container._reactRootContainer`
    
  425.         container._reactRootContainer = null;
    
  426.         unmarkContainerAsRoot(container);
    
  427.       });
    
  428.     });
    
  429.     // If you call unmountComponentAtNode twice in quick succession, you'll
    
  430.     // get `true` twice. That's probably fine?
    
  431.     return true;
    
  432.   } else {
    
  433.     if (__DEV__) {
    
  434.       const rootEl = getReactRootElementInContainer(container);
    
  435.       const hasNonRootReactChild = !!(rootEl && getInstanceFromNode(rootEl));
    
  436. 
    
  437.       // Check if the container itself is a React root node.
    
  438.       const isContainerReactRoot =
    
  439.         container.nodeType === ELEMENT_NODE &&
    
  440.         isValidContainerLegacy(container.parentNode) &&
    
  441.         // $FlowFixMe[prop-missing]
    
  442.         // $FlowFixMe[incompatible-use]
    
  443.         !!container.parentNode._reactRootContainer;
    
  444. 
    
  445.       if (hasNonRootReactChild) {
    
  446.         console.error(
    
  447.           "unmountComponentAtNode(): The node you're attempting to unmount " +
    
  448.             'was rendered by React and is not a top-level container. %s',
    
  449.           isContainerReactRoot
    
  450.             ? 'You may have accidentally passed in a React root node instead ' +
    
  451.                 'of its container.'
    
  452.             : 'Instead, have the parent component update its state and ' +
    
  453.                 'rerender in order to remove this component.',
    
  454.         );
    
  455.       }
    
  456.     }
    
  457. 
    
  458.     return false;
    
  459.   }
    
  460. }