From a3152eda5f89e20f056521855f7fa101ce50e4c3 Mon Sep 17 00:00:00 2001 From: Josh Story Date: Wed, 8 Feb 2023 11:32:38 -0800 Subject: [PATCH] support ReactDOM.render(..., document) without crashing (#26129) as reported in #26128 `ReactDOM.render(..., document)` crashed when `enableHostSingletons` was on. This is because it had a different way of clearing the container than `createRoot(document)`. I updated the legacy implementation to share the clearing behavior of `creatRoot` which will preserve the singleton instances. I also removed the warning saying not to use `document.body` as a container --- .../ReactDOMSingletonComponents-test.js | 17 +++++++++++++++++ .../react-dom/src/__tests__/ReactMount-test.js | 17 +++++++++++------ .../src/__tests__/validateDOMNesting-test.js | 18 +++++++++--------- .../react-dom/src/client/ReactDOMLegacy.js | 8 ++++---- 4 files changed, 41 insertions(+), 19 deletions(-) diff --git a/packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js b/packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js index 026302c18cc27..f78a7290b9c90 100644 --- a/packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMSingletonComponents-test.js @@ -13,6 +13,7 @@ let JSDOM; let Stream; let Scheduler; let React; +let ReactDOM; let ReactDOMClient; let ReactDOMFizzServer; let document; @@ -28,6 +29,7 @@ describe('ReactDOM HostSingleton', () => { JSDOM = require('jsdom').JSDOM; Scheduler = require('scheduler'); React = require('react'); + ReactDOM = require('react-dom'); ReactDOMClient = require('react-dom/client'); ReactDOMFizzServer = require('react-dom/server'); Stream = require('stream'); @@ -1007,4 +1009,19 @@ describe('ReactDOM HostSingleton', () => { , ); }); + + // https://github.com/facebook/react/issues/26128 + it('(#26128) does not throw when rendering at body', async () => { + ReactDOM.render(
, document.body); + }); + + // https://github.com/facebook/react/issues/26128 + it('(#26128) does not throw when rendering at ', async () => { + ReactDOM.render(, document.documentElement); + }); + + // https://github.com/facebook/react/issues/26128 + it('(#26128) does not throw when rendering at document', async () => { + ReactDOM.render(, document); + }); }); diff --git a/packages/react-dom/src/__tests__/ReactMount-test.js b/packages/react-dom/src/__tests__/ReactMount-test.js index c69ed03559a66..f383bec90f3cb 100644 --- a/packages/react-dom/src/__tests__/ReactMount-test.js +++ b/packages/react-dom/src/__tests__/ReactMount-test.js @@ -149,12 +149,17 @@ describe('ReactMount', () => { const iFrame = document.createElement('iframe'); document.body.appendChild(iFrame); - expect(() => - ReactDOM.render(
, iFrame.contentDocument.body), - ).toErrorDev( - 'Rendering components directly into document.body is discouraged', - {withoutStack: true}, - ); + if (gate(flags => flags.enableHostSingletons)) { + // HostSingletons make the warning for document.body unecessary + ReactDOM.render(
, iFrame.contentDocument.body); + } else { + expect(() => + ReactDOM.render(
, iFrame.contentDocument.body), + ).toErrorDev( + 'Rendering components directly into document.body is discouraged', + {withoutStack: true}, + ); + } }); it('should account for escaping on a checksum mismatch', () => { diff --git a/packages/react-dom/src/__tests__/validateDOMNesting-test.js b/packages/react-dom/src/__tests__/validateDOMNesting-test.js index f6ec407ff9bc3..ca1d921134785 100644 --- a/packages/react-dom/src/__tests__/validateDOMNesting-test.js +++ b/packages/react-dom/src/__tests__/validateDOMNesting-test.js @@ -28,9 +28,11 @@ function expectWarnings(tags, warnings = [], withoutStack = 0) { element = {element}; } - expect(() => ReactDOM.render(element, container)).toErrorDev(warnings, { - withoutStack, - }); + if (warnings.length) { + expect(() => ReactDOM.render(element, container)).toErrorDev(warnings, { + withoutStack, + }); + } } describe('validateDOMNesting', () => { @@ -39,8 +41,10 @@ describe('validateDOMNesting', () => { expectWarnings( ['body', 'datalist', 'option'], [ - 'render(): Rendering components directly into document.body is discouraged', - ], + gate(flags => !flags.enableHostSingletons) + ? 'render(): Rendering components directly into document.body is discouraged' + : null, + ].filter(Boolean), 1, ); expectWarnings(['div', 'a', 'object', 'a']); @@ -106,13 +110,9 @@ describe('validateDOMNesting', () => { expectWarnings( ['body', 'body'], [ - 'render(): Rendering components directly into document.body is discouraged', 'validateDOMNesting(...): cannot appear as a child of .\n' + ' in body (at **)', - 'Warning: You are mounting a new body component when a previous one has not first unmounted. It is an error to render more than one body component at a time and attributes and children of these components will likely fail in unpredictable ways. Please only render a single instance of and if you need to mount a new one, ensure any previous ones have unmounted first.\n' + - ' in body (at **)', ], - 1, ); } else { expectWarnings( diff --git a/packages/react-dom/src/client/ReactDOMLegacy.js b/packages/react-dom/src/client/ReactDOMLegacy.js index b1f06e6e15dbe..d2125b9adab02 100644 --- a/packages/react-dom/src/client/ReactDOMLegacy.js +++ b/packages/react-dom/src/client/ReactDOMLegacy.js @@ -14,6 +14,7 @@ import type { import type {FiberRoot} from 'react-reconciler/src/ReactInternalTypes'; import type {ReactNodeList} from 'shared/ReactTypes'; +import {clearContainer} from 'react-dom-bindings/src/client/ReactDOMHostConfig'; import { getInstanceFromNode, isContainerMarkedAsRoot, @@ -42,6 +43,7 @@ import {LegacyRoot} from 'react-reconciler/src/ReactRootTags'; import getComponentNameFromType from 'shared/getComponentNameFromType'; import ReactSharedInternals from 'shared/ReactSharedInternals'; import {has as hasInstance} from 'shared/ReactInstanceMap'; +import {enableHostSingletons} from '../../../shared/ReactFeatureFlags'; const ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner; @@ -79,6 +81,7 @@ if (__DEV__) { } if ( + !enableHostSingletons && container.nodeType === ELEMENT_NODE && ((container: any): Element).tagName && ((container: any): Element).tagName.toUpperCase() === 'BODY' @@ -152,10 +155,7 @@ function legacyCreateRootFromDOMContainer( return root; } else { // First clear any existing content. - let rootSibling; - while ((rootSibling = container.lastChild)) { - container.removeChild(rootSibling); - } + clearContainer(container); if (typeof callback === 'function') { const originalCallback = callback;