Skip to content

Commit cee5338

Browse files
committed
Consistently handle non-string dangerousSetInnerHTML.__html in SSR
1 parent 0072b59 commit cee5338

File tree

4 files changed

+103
-7
lines changed

4 files changed

+103
-7
lines changed

packages/react-dom/src/__tests__/ReactDOMServerIntegrationElements-test.js

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -485,16 +485,97 @@ describe('ReactDOMServerIntegration', () => {
485485
expect(e.tagName).toBe('BUTTON');
486486
});
487487

488-
itRenders('a div with dangerouslySetInnerHTML', async render => {
489-
const e = await render(
490-
<div dangerouslySetInnerHTML={{__html: "<span id='child'/>"}} />,
491-
);
488+
itRenders('a div with dangerouslySetInnerHTML number', async render => {
489+
// Put dangerouslySetInnerHTML one level deeper because otherwise
490+
// hydrating from a bad markup would cause a mismatch (since we don't
491+
// patch dangerouslySetInnerHTML as text content).
492+
const e = (await render(
493+
<div>
494+
<span dangerouslySetInnerHTML={{__html: 0}} />
495+
</div>,
496+
)).firstChild;
497+
expect(e.childNodes.length).toBe(1);
498+
expect(e.firstChild.nodeType).toBe(TEXT_NODE_TYPE);
499+
expect(e.textContent).toBe('0');
500+
});
501+
502+
itRenders('a div with dangerouslySetInnerHTML boolean', async render => {
503+
// Put dangerouslySetInnerHTML one level deeper because otherwise
504+
// hydrating from a bad markup would cause a mismatch (since we don't
505+
// patch dangerouslySetInnerHTML as text content).
506+
const e = (await render(
507+
<div>
508+
<span dangerouslySetInnerHTML={{__html: false}} />
509+
</div>,
510+
)).firstChild;
511+
expect(e.childNodes.length).toBe(1);
512+
expect(e.firstChild.nodeType).toBe(TEXT_NODE_TYPE);
513+
expect(e.firstChild.data).toBe('false');
514+
});
515+
516+
itRenders(
517+
'a div with dangerouslySetInnerHTML text string',
518+
async render => {
519+
// Put dangerouslySetInnerHTML one level deeper because otherwise
520+
// hydrating from a bad markup would cause a mismatch (since we don't
521+
// patch dangerouslySetInnerHTML as text content).
522+
const e = (await render(
523+
<div>
524+
<span dangerouslySetInnerHTML={{__html: 'hello'}} />
525+
</div>,
526+
)).firstChild;
527+
expect(e.childNodes.length).toBe(1);
528+
expect(e.firstChild.nodeType).toBe(TEXT_NODE_TYPE);
529+
expect(e.textContent).toBe('hello');
530+
},
531+
);
532+
533+
itRenders(
534+
'a div with dangerouslySetInnerHTML element string',
535+
async render => {
536+
const e = await render(
537+
<div dangerouslySetInnerHTML={{__html: "<span id='child'/>"}} />,
538+
);
539+
expect(e.childNodes.length).toBe(1);
540+
expect(e.firstChild.tagName).toBe('SPAN');
541+
expect(e.firstChild.getAttribute('id')).toBe('child');
542+
expect(e.firstChild.childNodes.length).toBe(0);
543+
},
544+
);
545+
546+
itRenders('a div with dangerouslySetInnerHTML object', async render => {
547+
const obj = {
548+
toString() {
549+
return "<span id='child'/>";
550+
},
551+
};
552+
const e = await render(<div dangerouslySetInnerHTML={{__html: obj}} />);
492553
expect(e.childNodes.length).toBe(1);
493554
expect(e.firstChild.tagName).toBe('SPAN');
494555
expect(e.firstChild.getAttribute('id')).toBe('child');
495556
expect(e.firstChild.childNodes.length).toBe(0);
496557
});
497558

559+
itRenders(
560+
'a div with dangerouslySetInnerHTML set to null',
561+
async render => {
562+
const e = await render(
563+
<div dangerouslySetInnerHTML={{__html: null}} />,
564+
);
565+
expect(e.childNodes.length).toBe(0);
566+
},
567+
);
568+
569+
itRenders(
570+
'a div with dangerouslySetInnerHTML set to undefined',
571+
async render => {
572+
const e = await render(
573+
<div dangerouslySetInnerHTML={{__html: undefined}} />,
574+
);
575+
expect(e.childNodes.length).toBe(0);
576+
},
577+
);
578+
498579
describe('newline-eating elements', function() {
499580
itRenders(
500581
'a newline-eating tag with content not starting with \\n',

packages/react-dom/src/__tests__/ReactDOMServerIntegrationReconnecting-test.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,18 @@ describe('ReactDOMServerIntegration', () => {
412412
<div dangerouslySetInnerHTML={{__html: "<span id='child2'/>"}} />,
413413
));
414414

415+
it('should error reconnecting a div with different text dangerouslySetInnerHTML', () =>
416+
expectMarkupMismatch(
417+
<div dangerouslySetInnerHTML={{__html: 'foo'}} />,
418+
<div dangerouslySetInnerHTML={{__html: 'bar'}} />,
419+
));
420+
421+
it('should error reconnecting a div with different number dangerouslySetInnerHTML', () =>
422+
expectMarkupMismatch(
423+
<div dangerouslySetInnerHTML={{__html: 10}} />,
424+
<div dangerouslySetInnerHTML={{__html: 20}} />,
425+
));
426+
415427
it('can explicitly ignore reconnecting a div with different dangerouslySetInnerHTML', () =>
416428
expectMarkupMatch(
417429
<div dangerouslySetInnerHTML={{__html: "<span id='child1'/>"}} />,

packages/react-dom/src/client/ReactDOMFiberComponent.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -987,9 +987,12 @@ export function diffHydratedProperties(
987987
) {
988988
// Noop
989989
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
990-
const rawHtml = nextProp ? nextProp[HTML] || '' : '';
991990
const serverHTML = domElement.innerHTML;
992-
const expectedHTML = normalizeHTML(domElement, rawHtml);
991+
const nextHtml = nextProp ? nextProp[HTML] : undefined;
992+
const expectedHTML = normalizeHTML(
993+
domElement,
994+
nextHtml != null ? nextHtml : '',
995+
);
993996
if (expectedHTML !== serverHTML) {
994997
warnForPropDifference(propKey, serverHTML, expectedHTML);
995998
}

packages/react-dom/src/client/ReactDOMHostConfig.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ export function shouldSetTextContent(type: string, props: Props): boolean {
249249
typeof props.children === 'number' ||
250250
(typeof props.dangerouslySetInnerHTML === 'object' &&
251251
props.dangerouslySetInnerHTML !== null &&
252-
typeof props.dangerouslySetInnerHTML.__html === 'string')
252+
props.dangerouslySetInnerHTML.__html != null)
253253
);
254254
}
255255

0 commit comments

Comments
 (0)