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 {ReactNodeList, ReactFormState} from 'shared/ReactTypes';
    
  11. import type {
    
  12.   FiberRoot,
    
  13.   TransitionTracingCallbacks,
    
  14. } from 'react-reconciler/src/ReactInternalTypes';
    
  15. 
    
  16. import {ReactDOMClientDispatcher} from 'react-dom-bindings/src/client/ReactFiberConfigDOM';
    
  17. import {queueExplicitHydrationTarget} from 'react-dom-bindings/src/events/ReactDOMEventReplaying';
    
  18. import {REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
    
  19. import {
    
  20.   enableFloat,
    
  21.   enableHostSingletons,
    
  22.   allowConcurrentByDefault,
    
  23.   disableCommentsAsDOMContainers,
    
  24.   enableAsyncActions,
    
  25.   enableFormActions,
    
  26. } from 'shared/ReactFeatureFlags';
    
  27. 
    
  28. import ReactDOMSharedInternals from '../ReactDOMSharedInternals';
    
  29. const {Dispatcher} = ReactDOMSharedInternals;
    
  30. if (enableFloat && typeof document !== 'undefined') {
    
  31.   // Set the default dispatcher to the client dispatcher
    
  32.   Dispatcher.current = ReactDOMClientDispatcher;
    
  33. }
    
  34. 
    
  35. export type RootType = {
    
  36.   render(children: ReactNodeList): void,
    
  37.   unmount(): void,
    
  38.   _internalRoot: FiberRoot | null,
    
  39.   ...
    
  40. };
    
  41. export type CreateRootOptions = {
    
  42.   unstable_strictMode?: boolean,
    
  43.   unstable_concurrentUpdatesByDefault?: boolean,
    
  44.   unstable_transitionCallbacks?: TransitionTracingCallbacks,
    
  45.   identifierPrefix?: string,
    
  46.   onRecoverableError?: (error: mixed) => void,
    
  47.   ...
    
  48. };
    
  49. 
    
  50. export type HydrateRootOptions = {
    
  51.   // Hydration options
    
  52.   onHydrated?: (suspenseNode: Comment) => void,
    
  53.   onDeleted?: (suspenseNode: Comment) => void,
    
  54.   // Options for all roots
    
  55.   unstable_strictMode?: boolean,
    
  56.   unstable_concurrentUpdatesByDefault?: boolean,
    
  57.   unstable_transitionCallbacks?: TransitionTracingCallbacks,
    
  58.   identifierPrefix?: string,
    
  59.   onRecoverableError?: (error: mixed) => void,
    
  60.   formState?: ReactFormState<any, any> | null,
    
  61.   ...
    
  62. };
    
  63. 
    
  64. import {
    
  65.   isContainerMarkedAsRoot,
    
  66.   markContainerAsRoot,
    
  67.   unmarkContainerAsRoot,
    
  68. } from 'react-dom-bindings/src/client/ReactDOMComponentTree';
    
  69. import {listenToAllSupportedEvents} from 'react-dom-bindings/src/events/DOMPluginEventSystem';
    
  70. import {
    
  71.   ELEMENT_NODE,
    
  72.   COMMENT_NODE,
    
  73.   DOCUMENT_NODE,
    
  74.   DOCUMENT_FRAGMENT_NODE,
    
  75. } from 'react-dom-bindings/src/client/HTMLNodeType';
    
  76. 
    
  77. import {
    
  78.   createContainer,
    
  79.   createHydrationContainer,
    
  80.   updateContainer,
    
  81.   findHostInstanceWithNoPortals,
    
  82.   flushSync,
    
  83.   isAlreadyRendering,
    
  84. } from 'react-reconciler/src/ReactFiberReconciler';
    
  85. import {ConcurrentRoot} from 'react-reconciler/src/ReactRootTags';
    
  86. 
    
  87. /* global reportError */
    
  88. const defaultOnRecoverableError =
    
  89.   typeof reportError === 'function'
    
  90.     ? // In modern browsers, reportError will dispatch an error event,
    
  91.       // emulating an uncaught JavaScript error.
    
  92.       reportError
    
  93.     : (error: mixed) => {
    
  94.         // In older browsers and test environments, fallback to console.error.
    
  95.         // eslint-disable-next-line react-internal/no-production-logging
    
  96.         console['error'](error);
    
  97.       };
    
  98. 
    
  99. // $FlowFixMe[missing-this-annot]
    
  100. function ReactDOMRoot(internalRoot: FiberRoot) {
    
  101.   this._internalRoot = internalRoot;
    
  102. }
    
  103. 
    
  104. // $FlowFixMe[prop-missing] found when upgrading Flow
    
  105. ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render =
    
  106.   // $FlowFixMe[missing-this-annot]
    
  107.   function (children: ReactNodeList): void {
    
  108.     const root = this._internalRoot;
    
  109.     if (root === null) {
    
  110.       throw new Error('Cannot update an unmounted root.');
    
  111.     }
    
  112. 
    
  113.     if (__DEV__) {
    
  114.       if (typeof arguments[1] === 'function') {
    
  115.         console.error(
    
  116.           'render(...): does not support the second callback argument. ' +
    
  117.             'To execute a side effect after rendering, declare it in a component body with useEffect().',
    
  118.         );
    
  119.       } else if (isValidContainer(arguments[1])) {
    
  120.         console.error(
    
  121.           'You passed a container to the second argument of root.render(...). ' +
    
  122.             "You don't need to pass it again since you already passed it to create the root.",
    
  123.         );
    
  124.       } else if (typeof arguments[1] !== 'undefined') {
    
  125.         console.error(
    
  126.           'You passed a second argument to root.render(...) but it only accepts ' +
    
  127.             'one argument.',
    
  128.         );
    
  129.       }
    
  130. 
    
  131.       const container = root.containerInfo;
    
  132. 
    
  133.       if (
    
  134.         !enableFloat &&
    
  135.         !enableHostSingletons &&
    
  136.         container.nodeType !== COMMENT_NODE
    
  137.       ) {
    
  138.         const hostInstance = findHostInstanceWithNoPortals(root.current);
    
  139.         if (hostInstance) {
    
  140.           if (hostInstance.parentNode !== container) {
    
  141.             console.error(
    
  142.               'render(...): It looks like the React-rendered content of the ' +
    
  143.                 'root container was removed without using React. This is not ' +
    
  144.                 'supported and will cause errors. Instead, call ' +
    
  145.                 "root.unmount() to empty a root's container.",
    
  146.             );
    
  147.           }
    
  148.         }
    
  149.       }
    
  150.     }
    
  151.     updateContainer(children, root, null, null);
    
  152.   };
    
  153. 
    
  154. // $FlowFixMe[prop-missing] found when upgrading Flow
    
  155. ReactDOMHydrationRoot.prototype.unmount = ReactDOMRoot.prototype.unmount =
    
  156.   // $FlowFixMe[missing-this-annot]
    
  157.   function (): void {
    
  158.     if (__DEV__) {
    
  159.       if (typeof arguments[0] === 'function') {
    
  160.         console.error(
    
  161.           'unmount(...): does not support a callback argument. ' +
    
  162.             'To execute a side effect after rendering, declare it in a component body with useEffect().',
    
  163.         );
    
  164.       }
    
  165.     }
    
  166.     const root = this._internalRoot;
    
  167.     if (root !== null) {
    
  168.       this._internalRoot = null;
    
  169.       const container = root.containerInfo;
    
  170.       if (__DEV__) {
    
  171.         if (isAlreadyRendering()) {
    
  172.           console.error(
    
  173.             'Attempted to synchronously unmount a root while React was already ' +
    
  174.               'rendering. React cannot finish unmounting the root until the ' +
    
  175.               'current render has completed, which may lead to a race condition.',
    
  176.           );
    
  177.         }
    
  178.       }
    
  179.       flushSync(() => {
    
  180.         updateContainer(null, root, null, null);
    
  181.       });
    
  182.       unmarkContainerAsRoot(container);
    
  183.     }
    
  184.   };
    
  185. 
    
  186. export function createRoot(
    
  187.   container: Element | Document | DocumentFragment,
    
  188.   options?: CreateRootOptions,
    
  189. ): RootType {
    
  190.   if (!isValidContainer(container)) {
    
  191.     throw new Error('createRoot(...): Target container is not a DOM element.');
    
  192.   }
    
  193. 
    
  194.   warnIfReactDOMContainerInDEV(container);
    
  195. 
    
  196.   let isStrictMode = false;
    
  197.   let concurrentUpdatesByDefaultOverride = false;
    
  198.   let identifierPrefix = '';
    
  199.   let onRecoverableError = defaultOnRecoverableError;
    
  200.   let transitionCallbacks = null;
    
  201. 
    
  202.   if (options !== null && options !== undefined) {
    
  203.     if (__DEV__) {
    
  204.       if ((options: any).hydrate) {
    
  205.         console.warn(
    
  206.           'hydrate through createRoot is deprecated. Use ReactDOMClient.hydrateRoot(container, <App />) instead.',
    
  207.         );
    
  208.       } else {
    
  209.         if (
    
  210.           typeof options === 'object' &&
    
  211.           options !== null &&
    
  212.           (options: any).$$typeof === REACT_ELEMENT_TYPE
    
  213.         ) {
    
  214.           console.error(
    
  215.             'You passed a JSX element to createRoot. You probably meant to ' +
    
  216.               'call root.render instead. ' +
    
  217.               'Example usage:\n\n' +
    
  218.               '  let root = createRoot(domContainer);\n' +
    
  219.               '  root.render(<App />);',
    
  220.           );
    
  221.         }
    
  222.       }
    
  223.     }
    
  224.     if (options.unstable_strictMode === true) {
    
  225.       isStrictMode = true;
    
  226.     }
    
  227.     if (
    
  228.       allowConcurrentByDefault &&
    
  229.       options.unstable_concurrentUpdatesByDefault === true
    
  230.     ) {
    
  231.       concurrentUpdatesByDefaultOverride = true;
    
  232.     }
    
  233.     if (options.identifierPrefix !== undefined) {
    
  234.       identifierPrefix = options.identifierPrefix;
    
  235.     }
    
  236.     if (options.onRecoverableError !== undefined) {
    
  237.       onRecoverableError = options.onRecoverableError;
    
  238.     }
    
  239.     if (options.unstable_transitionCallbacks !== undefined) {
    
  240.       transitionCallbacks = options.unstable_transitionCallbacks;
    
  241.     }
    
  242.   }
    
  243. 
    
  244.   const root = createContainer(
    
  245.     container,
    
  246.     ConcurrentRoot,
    
  247.     null,
    
  248.     isStrictMode,
    
  249.     concurrentUpdatesByDefaultOverride,
    
  250.     identifierPrefix,
    
  251.     onRecoverableError,
    
  252.     transitionCallbacks,
    
  253.   );
    
  254.   markContainerAsRoot(root.current, container);
    
  255.   Dispatcher.current = ReactDOMClientDispatcher;
    
  256. 
    
  257.   const rootContainerElement: Document | Element | DocumentFragment =
    
  258.     container.nodeType === COMMENT_NODE
    
  259.       ? (container.parentNode: any)
    
  260.       : container;
    
  261.   listenToAllSupportedEvents(rootContainerElement);
    
  262. 
    
  263.   // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
    
  264.   return new ReactDOMRoot(root);
    
  265. }
    
  266. 
    
  267. // $FlowFixMe[missing-this-annot]
    
  268. function ReactDOMHydrationRoot(internalRoot: FiberRoot) {
    
  269.   this._internalRoot = internalRoot;
    
  270. }
    
  271. function scheduleHydration(target: Node) {
    
  272.   if (target) {
    
  273.     queueExplicitHydrationTarget(target);
    
  274.   }
    
  275. }
    
  276. // $FlowFixMe[prop-missing] found when upgrading Flow
    
  277. ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = scheduleHydration;
    
  278. 
    
  279. export function hydrateRoot(
    
  280.   container: Document | Element,
    
  281.   initialChildren: ReactNodeList,
    
  282.   options?: HydrateRootOptions,
    
  283. ): RootType {
    
  284.   if (!isValidContainer(container)) {
    
  285.     throw new Error('hydrateRoot(...): Target container is not a DOM element.');
    
  286.   }
    
  287. 
    
  288.   warnIfReactDOMContainerInDEV(container);
    
  289. 
    
  290.   if (__DEV__) {
    
  291.     if (initialChildren === undefined) {
    
  292.       console.error(
    
  293.         'Must provide initial children as second argument to hydrateRoot. ' +
    
  294.           'Example usage: hydrateRoot(domContainer, <App />)',
    
  295.       );
    
  296.     }
    
  297.   }
    
  298. 
    
  299.   // For now we reuse the whole bag of options since they contain
    
  300.   // the hydration callbacks.
    
  301.   const hydrationCallbacks = options != null ? options : null;
    
  302. 
    
  303.   let isStrictMode = false;
    
  304.   let concurrentUpdatesByDefaultOverride = false;
    
  305.   let identifierPrefix = '';
    
  306.   let onRecoverableError = defaultOnRecoverableError;
    
  307.   let transitionCallbacks = null;
    
  308.   let formState = null;
    
  309.   if (options !== null && options !== undefined) {
    
  310.     if (options.unstable_strictMode === true) {
    
  311.       isStrictMode = true;
    
  312.     }
    
  313.     if (
    
  314.       allowConcurrentByDefault &&
    
  315.       options.unstable_concurrentUpdatesByDefault === true
    
  316.     ) {
    
  317.       concurrentUpdatesByDefaultOverride = true;
    
  318.     }
    
  319.     if (options.identifierPrefix !== undefined) {
    
  320.       identifierPrefix = options.identifierPrefix;
    
  321.     }
    
  322.     if (options.onRecoverableError !== undefined) {
    
  323.       onRecoverableError = options.onRecoverableError;
    
  324.     }
    
  325.     if (options.unstable_transitionCallbacks !== undefined) {
    
  326.       transitionCallbacks = options.unstable_transitionCallbacks;
    
  327.     }
    
  328.     if (enableAsyncActions && enableFormActions) {
    
  329.       if (options.formState !== undefined) {
    
  330.         formState = options.formState;
    
  331.       }
    
  332.     }
    
  333.   }
    
  334. 
    
  335.   const root = createHydrationContainer(
    
  336.     initialChildren,
    
  337.     null,
    
  338.     container,
    
  339.     ConcurrentRoot,
    
  340.     hydrationCallbacks,
    
  341.     isStrictMode,
    
  342.     concurrentUpdatesByDefaultOverride,
    
  343.     identifierPrefix,
    
  344.     onRecoverableError,
    
  345.     transitionCallbacks,
    
  346.     formState,
    
  347.   );
    
  348.   markContainerAsRoot(root.current, container);
    
  349.   Dispatcher.current = ReactDOMClientDispatcher;
    
  350.   // This can't be a comment node since hydration doesn't work on comment nodes anyway.
    
  351.   listenToAllSupportedEvents(container);
    
  352. 
    
  353.   // $FlowFixMe[invalid-constructor] Flow no longer supports calling new on functions
    
  354.   return new ReactDOMHydrationRoot(root);
    
  355. }
    
  356. 
    
  357. export function isValidContainer(node: any): boolean {
    
  358.   return !!(
    
  359.     node &&
    
  360.     (node.nodeType === ELEMENT_NODE ||
    
  361.       node.nodeType === DOCUMENT_NODE ||
    
  362.       node.nodeType === DOCUMENT_FRAGMENT_NODE ||
    
  363.       (!disableCommentsAsDOMContainers &&
    
  364.         node.nodeType === COMMENT_NODE &&
    
  365.         (node: any).nodeValue === ' react-mount-point-unstable '))
    
  366.   );
    
  367. }
    
  368. 
    
  369. // TODO: Remove this function which also includes comment nodes.
    
  370. // We only use it in places that are currently more relaxed.
    
  371. export function isValidContainerLegacy(node: any): boolean {
    
  372.   return !!(
    
  373.     node &&
    
  374.     (node.nodeType === ELEMENT_NODE ||
    
  375.       node.nodeType === DOCUMENT_NODE ||
    
  376.       node.nodeType === DOCUMENT_FRAGMENT_NODE ||
    
  377.       (node.nodeType === COMMENT_NODE &&
    
  378.         (node: any).nodeValue === ' react-mount-point-unstable '))
    
  379.   );
    
  380. }
    
  381. 
    
  382. function warnIfReactDOMContainerInDEV(container: any) {
    
  383.   if (__DEV__) {
    
  384.     if (
    
  385.       !enableHostSingletons &&
    
  386.       container.nodeType === ELEMENT_NODE &&
    
  387.       ((container: any): Element).tagName &&
    
  388.       ((container: any): Element).tagName.toUpperCase() === 'BODY'
    
  389.     ) {
    
  390.       console.error(
    
  391.         'createRoot(): Creating roots directly with document.body is ' +
    
  392.           'discouraged, since its children are often manipulated by third-party ' +
    
  393.           'scripts and browser extensions. This may lead to subtle ' +
    
  394.           'reconciliation issues. Try using a container element created ' +
    
  395.           'for your app.',
    
  396.       );
    
  397.     }
    
  398.     if (isContainerMarkedAsRoot(container)) {
    
  399.       if (container._reactRootContainer) {
    
  400.         console.error(
    
  401.           'You are calling ReactDOMClient.createRoot() on a container that was previously ' +
    
  402.             'passed to ReactDOM.render(). This is not supported.',
    
  403.         );
    
  404.       } else {
    
  405.         console.error(
    
  406.           'You are calling ReactDOMClient.createRoot() on a container that ' +
    
  407.             'has already been passed to createRoot() before. Instead, call ' +
    
  408.             'root.render() on the existing root instead if you want to update it.',
    
  409.         );
    
  410.       }
    
  411.     }
    
  412.   }
    
  413. }