diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js
index dc0631d1d0e08..39f9cf4c2ad4a 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js
@@ -485,16 +485,97 @@ describe('ReactDOMServerIntegration', () => {
expect(e.tagName).toBe('BUTTON');
});
- itRenders('a div with dangerouslySetInnerHTML', async render => {
- const e = await render(
-
"}} />,
- );
+ itRenders('a div with dangerouslySetInnerHTML number', async render => {
+ // Put dangerouslySetInnerHTML one level deeper because otherwise
+ // hydrating from a bad markup would cause a mismatch (since we don't
+ // patch dangerouslySetInnerHTML as text content).
+ const e = (await render(
+
+
+
,
+ )).firstChild;
+ expect(e.childNodes.length).toBe(1);
+ expect(e.firstChild.nodeType).toBe(TEXT_NODE_TYPE);
+ expect(e.textContent).toBe('0');
+ });
+
+ itRenders('a div with dangerouslySetInnerHTML boolean', async render => {
+ // Put dangerouslySetInnerHTML one level deeper because otherwise
+ // hydrating from a bad markup would cause a mismatch (since we don't
+ // patch dangerouslySetInnerHTML as text content).
+ const e = (await render(
+
+
+
,
+ )).firstChild;
+ expect(e.childNodes.length).toBe(1);
+ expect(e.firstChild.nodeType).toBe(TEXT_NODE_TYPE);
+ expect(e.firstChild.data).toBe('false');
+ });
+
+ itRenders(
+ 'a div with dangerouslySetInnerHTML text string',
+ async render => {
+ // Put dangerouslySetInnerHTML one level deeper because otherwise
+ // hydrating from a bad markup would cause a mismatch (since we don't
+ // patch dangerouslySetInnerHTML as text content).
+ const e = (await render(
+
+
+
,
+ )).firstChild;
+ expect(e.childNodes.length).toBe(1);
+ expect(e.firstChild.nodeType).toBe(TEXT_NODE_TYPE);
+ expect(e.textContent).toBe('hello');
+ },
+ );
+
+ itRenders(
+ 'a div with dangerouslySetInnerHTML element string',
+ async render => {
+ const e = await render(
+ "}} />,
+ );
+ expect(e.childNodes.length).toBe(1);
+ expect(e.firstChild.tagName).toBe('SPAN');
+ expect(e.firstChild.getAttribute('id')).toBe('child');
+ expect(e.firstChild.childNodes.length).toBe(0);
+ },
+ );
+
+ itRenders('a div with dangerouslySetInnerHTML object', async render => {
+ const obj = {
+ toString() {
+ return "";
+ },
+ };
+ const e = await render();
expect(e.childNodes.length).toBe(1);
expect(e.firstChild.tagName).toBe('SPAN');
expect(e.firstChild.getAttribute('id')).toBe('child');
expect(e.firstChild.childNodes.length).toBe(0);
});
+ itRenders(
+ 'a div with dangerouslySetInnerHTML set to null',
+ async render => {
+ const e = await render(
+ ,
+ );
+ expect(e.childNodes.length).toBe(0);
+ },
+ );
+
+ itRenders(
+ 'a div with dangerouslySetInnerHTML set to undefined',
+ async render => {
+ const e = await render(
+ ,
+ );
+ expect(e.childNodes.length).toBe(0);
+ },
+ );
+
describe('newline-eating elements', function() {
itRenders(
'a newline-eating tag with content not starting with \\n',
diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js
index c10450c436f1d..79e90e6555fa8 100644
--- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js
+++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js
@@ -412,6 +412,40 @@ describe('ReactDOMServerIntegration', () => {
"}} />,
));
+ it('should error reconnecting a div with different text dangerouslySetInnerHTML', () =>
+ expectMarkupMismatch(
+ ,
+ ,
+ ));
+
+ it('should error reconnecting a div with different number dangerouslySetInnerHTML', () =>
+ expectMarkupMismatch(
+ ,
+ ,
+ ));
+
+ it('should error reconnecting a div with different object dangerouslySetInnerHTML', () =>
+ expectMarkupMismatch(
+ ,
+ ,
+ ));
+
it('can explicitly ignore reconnecting a div with different dangerouslySetInnerHTML', () =>
expectMarkupMatch(
"}} />,
diff --git a/packages/react-dom/src/client/ReactDOMFiberComponent.js b/packages/react-dom/src/client/ReactDOMFiberComponent.js
index 3928541fda88c..1d94ab33c91d9 100644
--- a/packages/react-dom/src/client/ReactDOMFiberComponent.js
+++ b/packages/react-dom/src/client/ReactDOMFiberComponent.js
@@ -987,9 +987,12 @@ export function diffHydratedProperties(
) {
// Noop
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
- const rawHtml = nextProp ? nextProp[HTML] || '' : '';
const serverHTML = domElement.innerHTML;
- const expectedHTML = normalizeHTML(domElement, rawHtml);
+ const nextHtml = nextProp ? nextProp[HTML] : undefined;
+ const expectedHTML = normalizeHTML(
+ domElement,
+ nextHtml != null ? nextHtml : '',
+ );
if (expectedHTML !== serverHTML) {
warnForPropDifference(propKey, serverHTML, expectedHTML);
}
diff --git a/packages/react-dom/src/client/ReactDOMHostConfig.js b/packages/react-dom/src/client/ReactDOMHostConfig.js
index 41d7253f4b1fc..0b3d930a4b673 100644
--- a/packages/react-dom/src/client/ReactDOMHostConfig.js
+++ b/packages/react-dom/src/client/ReactDOMHostConfig.js
@@ -249,7 +249,7 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
typeof props.children === 'number' ||
(typeof props.dangerouslySetInnerHTML === 'object' &&
props.dangerouslySetInnerHTML !== null &&
- typeof props.dangerouslySetInnerHTML.__html === 'string')
+ props.dangerouslySetInnerHTML.__html != null)
);
}