Repository: facebook/react
Pull request: Fix createRoot/hydrateRoot container signature

The internal Container type represents the types of containers that React can support in its internals that deal with containers.

This didn't include DocumentFragment which we support specifically for rendering into shadow roots.

However, not all types makes sense to pass into the createRoot API.

One of those is comment nodes that is deprecated and we don't really fully support. It really only exists for FB legacy.

For createRoot it doesn't make sense to pass a Document since that will try to empty the document which removes the HTML tag which doesn't work. Documents can only be passed to hydrateRoot.

Conversely I'm not sure we actually support hydrating a shadow root properly so I excluded DocumentFragment from hydrateRoot.

Changed files:
filename: packages/react-dom/client.js
patch: @@ -9,7 +9,6 @@
 
 'use strict';
 
-import type {Container} from './src/client/ReactDOMHostConfig';
 import type {ReactNodeList} from 'shared/ReactTypes';
 import type {
   RootType,
@@ -24,7 +23,7 @@ import {
 } from './';
 
 export function createRoot(
-  container: Container,
+  container: Element | DocumentFragment,
   options?: CreateRootOptions,
 ): RootType {
   if (__DEV__) {
@@ -40,7 +39,7 @@ export function createRoot(
 }
 
 export function hydrateRoot(
-  container: Container,
+  container: Document | Element,
   children: ReactNodeList,
   options?: HydrateRootOptions,
 ): RootType {

filename: packages/react-dom/src/client/ReactDOM.js
patch: @@ -148,7 +148,7 @@ const Internals = {
 };
 
 function createRoot(
-  container: Container,
+  container: Element | DocumentFragment,
   options?: CreateRootOptions,
 ): RootType {
   if (__DEV__) {
@@ -163,7 +163,7 @@ function createRoot(
 }
 
 function hydrateRoot(
-  container: Container,
+  container: Document | Element,
   initialChildren: ReactNodeList,
   options?: HydrateRootOptions,
 ): RootType {

filename: packages/react-dom/src/client/ReactDOMComponent.js
patch: @@ -259,7 +259,7 @@ export function checkForUnmatchedText(
 }
 
 function getOwnerDocumentFromRootContainer(
-  rootContainerElement: Element | Document,
+  rootContainerElement: Element | Document | DocumentFragment,
 ): Document {
   return rootContainerElement.nodeType === DOCUMENT_NODE
     ? (rootContainerElement: any)
@@ -284,7 +284,7 @@ export function trapClickOnNonInteractiveElement(node: HTMLElement) {
 function setInitialDOMProperties(
   tag: string,
   domElement: Element,
-  rootContainerElement: Element | Document,
+  rootContainerElement: Element | Document | DocumentFragment,
   nextProps: Object,
   isCustomComponentTag: boolean,
 ): void {
@@ -371,7 +371,7 @@ function updateDOMProperties(
 export function createElement(
   type: string,
   props: Object,
-  rootContainerElement: Element | Document,
+  rootContainerElement: Element | Document | DocumentFragment,
   parentNamespace: string,
 ): Element {
   let isCustomComponentTag;
@@ -477,7 +477,7 @@ export function createElement(
 
 export function createTextNode(
   text: string,
-  rootContainerElement: Element | Document,
+  rootContainerElement: Element | Document | DocumentFragment,
 ): Text {
   return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(
     text,
@@ -488,7 +488,7 @@ export function setInitialProperties(
   domElement: Element,
   tag: string,
   rawProps: Object,
-  rootContainerElement: Element | Document,
+  rootContainerElement: Element | Document | DocumentFragment,
 ): void {
   const isCustomComponentTag = isCustomComponent(tag, rawProps);
   if (__DEV__) {
@@ -614,7 +614,7 @@ export function diffProperties(
   tag: string,
   lastRawProps: Object,
   nextRawProps: Object,
-  rootContainerElement: Element | Document,
+  rootContainerElement: Element | Document | DocumentFragment,
 ): null | Array<mixed> {
   if (__DEV__) {
     validatePropertiesInDevelopment(tag, nextRawProps);
@@ -867,7 +867,7 @@ export function diffHydratedProperties(
   tag: string,
   rawProps: Object,
   parentNamespace: string,
-  rootContainerElement: Element | Document,
+  rootContainerElement: Element | Document | DocumentFragment,
   isConcurrentMode: boolean,
   shouldWarnDev: boolean,
 ): null | Array<mixed> {
@@ -1200,7 +1200,7 @@ export function diffHydratedText(
 }
 
 export function warnForDeletedHydratableElement(
-  parentNode: Element | Document,
+  parentNode: Element | Document | DocumentFragment,
   child: Element,
 ) {
   if (__DEV__) {
@@ -1217,7 +1217,7 @@ export function warnForDeletedHydratableElement(
 }
 
 export function warnForDeletedHydratableText(
-  parentNode: Element | Document,
+  parentNode: Element | Document | DocumentFragment,
   child: Text,
 ) {
   if (__DEV__) {
@@ -1234,7 +1234,7 @@ export function warnForDeletedHydratableText(
 }
 
 export function warnForInsertedHydratedElement(
-  parentNode: Element | Document,
+  parentNode: Element | Document | DocumentFragment,
   tag: string,
   props: Object,
 ) {
@@ -1252,7 +1252,7 @@ export function warnForInsertedHydratedElement(
 }
 
 export function warnForInsertedHydratedText(
-  parentNode: Element | Document,
+  parentNode: Element | Document | DocumentFragment,
   text: string,
 ) {
   if (__DEV__) {

filename: packages/react-dom/src/client/ReactDOMHostConfig.js
patch: @@ -107,7 +107,8 @@ export type EventTargetChildElement = {
 };
 export type Container =
   | (Element & {_reactRootContainer?: FiberRoot, ...})
-  | (Document & {_reactRootContainer?: FiberRoot, ...});
+  | (Document & {_reactRootContainer?: FiberRoot, ...})
+  | (DocumentFragment & {_reactRootContainer?: FiberRoot, ...});
 export type Instance = Element;
 export type TextInstance = Text;
 export type SuspenseInstance = Comment & {_reactRetry?: () => void, ...};

filename: packages/react-dom/src/client/ReactDOMRoot.js
patch: @@ -7,7 +7,6 @@
  * @flow
  */
 
-import type {Container} from './ReactDOMHostConfig';
 import type {MutableSource, ReactNodeList} from 'shared/ReactTypes';
 import type {
   FiberRoot,
@@ -165,7 +164,7 @@ ReactDOMHydrationRoot.prototype.unmount = ReactDOMRoot.prototype.unmount = funct
 };
 
 export function createRoot(
-  container: Container,
+  container: Element | DocumentFragment,
   options?: CreateRootOptions,
 ): RootType {
   if (!isValidContainer(container)) {
@@ -235,8 +234,10 @@ export function createRoot(
   );
   markContainerAsRoot(root.current, container);
 
-  const rootContainerElement =
-    container.nodeType === COMMENT_NODE ? container.parentNode : container;
+  const rootContainerElement: Document | Element | DocumentFragment =
+    container.nodeType === COMMENT_NODE
+      ? (container.parentNode: any)
+      : container;
   listenToAllSupportedEvents(rootContainerElement);
 
   return new ReactDOMRoot(root);
@@ -253,7 +254,7 @@ function scheduleHydration(target: Node) {
 ReactDOMHydrationRoot.prototype.unstable_scheduleHydration = scheduleHydration;
 
 export function hydrateRoot(
-  container: Container,
+  container: Document | Element,
   initialChildren: ReactNodeList,
   options?: HydrateRootOptions,
 ): RootType {
@@ -351,7 +352,7 @@ export function isValidContainerLegacy(node: any): boolean {
   );
 }
 
-function warnIfReactDOMContainerInDEV(container) {
+function warnIfReactDOMContainerInDEV(container: any) {
   if (__DEV__) {
     if (
       container.nodeType === ELEMENT_NODE &&