Skip to content

Commit da5c87b

Browse files
mridgwaygaearon
authored andcommitted
Fixes children when using dangerouslySetInnerHtml in a selected <option> (#13078)
* Fixes children when using dangerouslySetInnerHtml in a selected <option> This fixes an inadvertent cast of undefined children to an empty string when creating an option tag that will be selected: ``` <select defaultValue="test"> <option value='test' dangerouslySetInnerHTML={{ __html: '&rlm; test'}} /> </select> ``` This causes an invariant error because both children and dangerouslySetInnerHTML are set. * PR fix and new ReactDOMServerIntegrationForms test * Account for null case * Combine test cases into single test * Add tests for failure cases * Fix lint
1 parent a960d18 commit da5c87b

File tree

3 files changed

+111
-1
lines changed

3 files changed

+111
-1
lines changed

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -443,6 +443,37 @@ describe('ReactDOMSelect', () => {
443443
expect(markup).not.toContain('<option selected="" value="gorilla"');
444444
});
445445

446+
it('should support server-side rendering with dangerouslySetInnerHTML', () => {
447+
const stub = (
448+
<select defaultValue="giraffe">
449+
<option
450+
value="monkey"
451+
dangerouslySetInnerHTML={{
452+
__html: 'A monkey!',
453+
}}>
454+
{undefined}
455+
</option>
456+
<option
457+
value="giraffe"
458+
dangerouslySetInnerHTML={{
459+
__html: 'A giraffe!',
460+
}}>
461+
{null}
462+
</option>
463+
<option
464+
value="gorilla"
465+
dangerouslySetInnerHTML={{
466+
__html: 'A gorilla!',
467+
}}
468+
/>
469+
</select>
470+
);
471+
const markup = ReactDOMServer.renderToString(stub);
472+
expect(markup).toContain('<option selected="" value="giraffe"');
473+
expect(markup).not.toContain('<option selected="" value="monkey"');
474+
expect(markup).not.toContain('<option selected="" value="gorilla"');
475+
});
476+
446477
it('should support server-side rendering with multiple', () => {
447478
const stub = (
448479
<select multiple={true} value={['giraffe', 'gorilla']} onChange={noop}>

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

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const {
3535
resetModules,
3636
itRenders,
3737
itClientRenders,
38+
itThrowsWhenRendering,
3839
renderIntoDom,
3940
serverRender,
4041
} = ReactDOMServerIntegrationUtils(initModules);
@@ -327,6 +328,81 @@ describe('ReactDOMServerIntegration', () => {
327328
expectSelectValue(e, 'bar');
328329
});
329330

331+
itRenders(
332+
'a select with options that use dangerouslySetInnerHTML',
333+
async render => {
334+
const e = await render(
335+
<select defaultValue="baz" value="bar" readOnly={true}>
336+
<option
337+
id="foo"
338+
value="foo"
339+
dangerouslySetInnerHTML={{
340+
__html: 'Foo',
341+
}}>
342+
{undefined}
343+
</option>
344+
<option
345+
id="bar"
346+
value="bar"
347+
dangerouslySetInnerHTML={{
348+
__html: 'Bar',
349+
}}>
350+
{null}
351+
</option>
352+
<option
353+
id="baz"
354+
value="baz"
355+
dangerouslySetInnerHTML={{
356+
__html: 'Baz',
357+
}}
358+
/>
359+
</select>,
360+
1,
361+
);
362+
expectSelectValue(e, 'bar');
363+
},
364+
);
365+
366+
itThrowsWhenRendering(
367+
'a select with option that uses dangerouslySetInnerHTML and 0 as child',
368+
async render => {
369+
await render(
370+
<select defaultValue="baz" value="foo" readOnly={true}>
371+
<option
372+
id="foo"
373+
value="foo"
374+
dangerouslySetInnerHTML={{
375+
__html: 'Foo',
376+
}}>
377+
{0}
378+
</option>
379+
</select>,
380+
1,
381+
);
382+
},
383+
'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
384+
);
385+
386+
itThrowsWhenRendering(
387+
'a select with option that uses dangerouslySetInnerHTML and empty string as child',
388+
async render => {
389+
await render(
390+
<select defaultValue="baz" value="foo" readOnly={true}>
391+
<option
392+
id="foo"
393+
value="foo"
394+
dangerouslySetInnerHTML={{
395+
__html: 'Foo',
396+
}}>
397+
{''}
398+
</option>
399+
</select>,
400+
1,
401+
);
402+
},
403+
'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
404+
);
405+
330406
itRenders(
331407
'a select value overriding defaultValue no matter the prop order',
332408
async render => {

packages/react-dom/src/server/ReactPartialRenderer.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -258,7 +258,10 @@ function flattenTopLevelChildren(children: mixed): FlatReactChildren {
258258
return [fragmentChildElement];
259259
}
260260

261-
function flattenOptionChildren(children: mixed): string {
261+
function flattenOptionChildren(children: mixed): ?string {
262+
if (children === undefined || children === null) {
263+
return children;
264+
}
262265
let content = '';
263266
// Flatten children and warn if they aren't strings or numbers;
264267
// invalid types are ignored.

0 commit comments

Comments
 (0)