From cee533854056b87d2f0933eef7a8a39d43d0e690 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Thu, 9 Aug 2018 16:14:32 +0100 Subject: [PATCH] Consistently handle non-string dangerousSetInnerHTML.__html in SSR --- .../ReactDOMServerIntegrationElements-test.js | 89 ++++++++++++++++++- ...ctDOMServerIntegrationReconnecting-test.js | 12 +++ .../src/client/ReactDOMFiberComponent.js | 7 +- .../src/client/ReactDOMHostConfig.js | 2 +- 4 files changed, 103 insertions(+), 7 deletions(-) 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..5c83018197dae 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js @@ -412,6 +412,18 @@ 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('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) ); }