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: '‏ 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
Michael Ridgway committed
Jun 21, 2018 at 12:21 UTC
da5c87bdfa287b8493b6e40a02acbe9896fd554a
3 files changed
+111
-1
packages/react-dom/src/__tests__/ReactDOMSelect-test.js
+31
@@ -443,6 +443,37 @@ describe('ReactDOMSelect', () => {
443
expect(markup).not.toContain('<option selected="" value="gorilla"');
444
});
445
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
+
477
it('should support server-side rendering with multiple', () => {
478
const stub = (
479
<select multiple={true} value={['giraffe', 'gorilla']} onChange={noop}>
packages/react-dom/src/__tests__/ReactDOMServerIntegrationForms-test.js
+76
@@ -35,6 +35,7 @@ const {
35
resetModules,
36
itRenders,
37
itClientRenders,
38
+ itThrowsWhenRendering,
39
renderIntoDom,
40
serverRender,
41
} = ReactDOMServerIntegrationUtils(initModules);
@@ -327,6 +328,81 @@ describe('ReactDOMServerIntegration', () => {
328
expectSelectValue(e, 'bar');
329
});
330
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
+
406
itRenders(
407
'a select value overriding defaultValue no matter the prop order',
408
async render => {
packages/react-dom/src/server/ReactPartialRenderer.js
+4
-1
@@ -258,7 +258,10 @@ function flattenTopLevelChildren(children: mixed): FlatReactChildren {
258
return [fragmentChildElement];
259
}
260
261
-function flattenOptionChildren(children: mixed): string {
261
+function flattenOptionChildren(children: mixed): ?string {
262
+ if (children === undefined || children === null) {
263
+ return children;
264
+ }
265
let content = '';
266
// Flatten children and warn if they aren't strings or numbers;
267
// invalid types are ignored.