@samitouri / QOS-React / commits / da5c87bdfa

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

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.