Add disable <textarea/> children flag (#17874)
Dominic Gannaway committed
Jan 20, 2020 at 15:12 UTC
9fd760ce75fd76716888997852ca85394eeab49a
9 files changed
+186
-88
packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
+161
-75
@@ -19,6 +19,8 @@ describe('ReactDOMTextarea', () => {
19
20
let renderTextarea;
21
22
+ const ReactFeatureFlags = require('shared/ReactFeatureFlags');
23
+
24
beforeEach(() => {
25
jest.resetModules();
26
@@ -287,23 +289,58 @@ describe('ReactDOMTextarea', () => {
289
}
290
});
291
290
- it('should treat children like `defaultValue`', () => {
291
- const container = document.createElement('div');
292
- let stub = <textarea>giraffe</textarea>;
293
- let node;
292
+ if (ReactFeatureFlags.disableTextareaChildren) {
293
+ it('should ignore children content', () => {
294
+ const container = document.createElement('div');
295
+ let stub = <textarea>giraffe</textarea>;
296
+ let node;
297
295
- expect(() => {
296
- node = renderTextarea(stub, container);
297
- }).toErrorDev(
298
- 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
299
- );
298
+ expect(() => {
299
+ node = renderTextarea(stub, container);
300
+ }).toErrorDev(
301
+ 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
302
+ );
303
+ expect(node.value).toBe('');
304
+ // Changing children should do nothing, it functions like `defaultValue`.
305
+ stub = ReactDOM.render(<textarea>gorilla</textarea>, container);
306
+ expect(node.value).toEqual('');
307
+ });
308
+ }
309
301
- expect(node.value).toBe('giraffe');
310
+ if (ReactFeatureFlags.disableTextareaChildren) {
311
+ it('should receive defaultValue and still ignore children content', () => {
312
+ let node;
313
303
- // Changing children should do nothing, it functions like `defaultValue`.
304
- stub = ReactDOM.render(<textarea>gorilla</textarea>, container);
305
- expect(node.value).toEqual('giraffe');
306
- });
314
+ expect(() => {
315
+ node = renderTextarea(
316
+ <textarea defaultValue="dragon">monkey</textarea>,
317
+ );
318
+ }).toErrorDev(
319
+ 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
320
+ );
321
+ expect(node.value).toBe('dragon');
322
+ });
323
+ }
324
+
325
+ if (!ReactFeatureFlags.disableTextareaChildren) {
326
+ it('should treat children like `defaultValue`', () => {
327
+ const container = document.createElement('div');
328
+ let stub = <textarea>giraffe</textarea>;
329
+ let node;
330
+
331
+ expect(() => {
332
+ node = renderTextarea(stub, container);
333
+ }).toErrorDev(
334
+ 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
335
+ );
336
+
337
+ expect(node.value).toBe('giraffe');
338
+
339
+ // Changing children should do nothing, it functions like `defaultValue`.
340
+ stub = ReactDOM.render(<textarea>gorilla</textarea>, container);
341
+ expect(node.value).toEqual('giraffe');
342
+ });
343
+ }
344
345
it('should keep value when switching to uncontrolled element if not changed', () => {
346
const container = document.createElement('div');
@@ -342,71 +379,120 @@ describe('ReactDOMTextarea', () => {
379
expect(node.value).toEqual('puppies');
380
});
381
345
- it('should allow numbers as children', () => {
346
- let node;
347
- expect(() => {
348
- node = renderTextarea(<textarea>{17}</textarea>);
349
- }).toErrorDev(
350
- 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
351
- );
352
- expect(node.value).toBe('17');
353
- });
354
-
355
- it('should allow booleans as children', () => {
356
- let node;
357
- expect(() => {
358
- node = renderTextarea(<textarea>{false}</textarea>);
359
- }).toErrorDev(
360
- 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
361
- );
362
- expect(node.value).toBe('false');
363
- });
364
-
365
- it('should allow objects as children', () => {
366
- const obj = {
367
- toString: function() {
368
- return 'sharkswithlasers';
369
- },
370
- };
371
- let node;
372
- expect(() => {
373
- node = renderTextarea(<textarea>{obj}</textarea>);
374
- }).toErrorDev(
375
- 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
376
- );
377
- expect(node.value).toBe('sharkswithlasers');
378
- });
379
-
380
- it('should throw with multiple or invalid children', () => {
381
- expect(() => {
382
- expect(() =>
383
- ReactTestUtils.renderIntoDocument(
384
- <textarea>
385
- {'hello'}
386
- {'there'}
387
- </textarea>,
388
- ),
389
- ).toThrow('<textarea> can only have at most one child');
390
- }).toErrorDev(
391
- 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
392
- );
382
+ if (ReactFeatureFlags.disableTextareaChildren) {
383
+ it('should ignore numbers as children', () => {
384
+ let node;
385
+ expect(() => {
386
+ node = renderTextarea(<textarea>{17}</textarea>);
387
+ }).toErrorDev(
388
+ 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
389
+ );
390
+ expect(node.value).toBe('');
391
+ });
392
+ }
393
+
394
+ if (!ReactFeatureFlags.disableTextareaChildren) {
395
+ it('should allow numbers as children', () => {
396
+ let node;
397
+ expect(() => {
398
+ node = renderTextarea(<textarea>{17}</textarea>);
399
+ }).toErrorDev(
400
+ 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
401
+ );
402
+ expect(node.value).toBe('17');
403
+ });
404
+ }
405
+
406
+ if (ReactFeatureFlags.disableTextareaChildren) {
407
+ it('should ignore booleans as children', () => {
408
+ let node;
409
+ expect(() => {
410
+ node = renderTextarea(<textarea>{false}</textarea>);
411
+ }).toErrorDev(
412
+ 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
413
+ );
414
+ expect(node.value).toBe('');
415
+ });
416
+ }
417
+
418
+ if (!ReactFeatureFlags.disableTextareaChildren) {
419
+ it('should allow booleans as children', () => {
420
+ let node;
421
+ expect(() => {
422
+ node = renderTextarea(<textarea>{false}</textarea>);
423
+ }).toErrorDev(
424
+ 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
425
+ );
426
+ expect(node.value).toBe('false');
427
+ });
428
+ }
429
+
430
+ if (ReactFeatureFlags.disableTextareaChildren) {
431
+ it('should ignore objects as children', () => {
432
+ const obj = {
433
+ toString: function() {
434
+ return 'sharkswithlasers';
435
+ },
436
+ };
437
+ let node;
438
+ expect(() => {
439
+ node = renderTextarea(<textarea>{obj}</textarea>);
440
+ }).toErrorDev(
441
+ 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
442
+ );
443
+ expect(node.value).toBe('');
444
+ });
445
+ }
446
+
447
+ if (!ReactFeatureFlags.disableTextareaChildren) {
448
+ it('should allow objects as children', () => {
449
+ const obj = {
450
+ toString: function() {
451
+ return 'sharkswithlasers';
452
+ },
453
+ };
454
+ let node;
455
+ expect(() => {
456
+ node = renderTextarea(<textarea>{obj}</textarea>);
457
+ }).toErrorDev(
458
+ 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
459
+ );
460
+ expect(node.value).toBe('sharkswithlasers');
461
+ });
462
+ }
463
394
- let node;
395
- expect(() => {
396
- expect(
397
- () =>
398
- (node = renderTextarea(
464
+ if (!ReactFeatureFlags.disableTextareaChildren) {
465
+ it('should throw with multiple or invalid children', () => {
466
+ expect(() => {
467
+ expect(() =>
468
+ ReactTestUtils.renderIntoDocument(
469
<textarea>
400
- <strong />
470
+ {'hello'}
471
+ {'there'}
472
</textarea>,
402
- )),
403
- ).not.toThrow();
404
- }).toErrorDev(
405
- 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
406
- );
473
+ ),
474
+ ).toThrow('<textarea> can only have at most one child');
475
+ }).toErrorDev(
476
+ 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
477
+ );
478
408
- expect(node.value).toBe('[object Object]');
409
- });
479
+ let node;
480
+ expect(() => {
481
+ expect(
482
+ () =>
483
+ (node = renderTextarea(
484
+ <textarea>
485
+ <strong />
486
+ </textarea>,
487
+ )),
488
+ ).not.toThrow();
489
+ }).toErrorDev(
490
+ 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
491
+ );
492
+
493
+ expect(node.value).toBe('[object Object]');
494
+ });
495
+ }
496
497
it('should unmount', () => {
498
const container = document.createElement('div');
packages/react-dom/src/client/ReactDOMTextarea.js
+15
-13
@@ -14,6 +14,8 @@ import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCur
14
import {getToStringValue, toString} from './ToStringValue';
15
import type {ToStringValue} from './ToStringValue';
16
17
+import {disableTextareaChildren} from 'shared/ReactFeatureFlags';
18
+
19
let didWarnValDefaultVal = false;
20
21
type TextAreaWithWrapperState = HTMLTextAreaElement & {|
@@ -85,9 +87,7 @@ export function initWrapperState(element: Element, props: Object) {
87
88
// Only bother fetching default value if we're going to use it
89
if (initialValue == null) {
88
- let defaultValue = props.defaultValue;
89
- // TODO (yungsters): Remove support for children content in <textarea>.
90
- let children = props.children;
90
+ let {children, defaultValue} = props;
91
if (children != null) {
92
if (__DEV__) {
93
console.error(
@@ -95,19 +95,21 @@ export function initWrapperState(element: Element, props: Object) {
95
'children on <textarea>.',
96
);
97
}
98
- invariant(
99
- defaultValue == null,
100
- 'If you supply `defaultValue` on a <textarea>, do not pass children.',
101
- );
102
- if (Array.isArray(children)) {
98
+ if (!disableTextareaChildren) {
99
invariant(
104
- children.length <= 1,
105
- '<textarea> can only have at most one child.',
100
+ defaultValue == null,
101
+ 'If you supply `defaultValue` on a <textarea>, do not pass children.',
102
);
107
- children = children[0];
103
+ if (Array.isArray(children)) {
104
+ invariant(
105
+ children.length <= 1,
106
+ '<textarea> can only have at most one child.',
107
+ );
108
+ children = children[0];
109
+ }
110
+
111
+ defaultValue = children;
112
}
109
-
110
- defaultValue = children;
113
}
114
if (defaultValue == null) {
115
defaultValue = '';
packages/shared/ReactFeatureFlags.js
+3
@@ -105,3 +105,6 @@ export const disableLegacyContext = false;
105
106
// Disables React.createFactory
107
export const disableCreateFactory = false;
108
+
109
+// Disables children for <textarea> elements
110
+export const disableTextareaChildren = false;
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1
@@ -46,6 +46,7 @@ export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
46
export const enableTrainModelFix = false;
47
export const enableTrustedTypesIntegration = false;
48
export const disableCreateFactory = false;
49
+export const disableTextareaChildren = false;
50
51
// Only used in www builds.
52
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1
@@ -41,6 +41,7 @@ export const enableTrainModelFix = false;
41
export const enableTrustedTypesIntegration = false;
42
export const enableNativeTargetAsInstance = false;
43
export const disableCreateFactory = false;
44
+export const disableTextareaChildren = false;
45
46
// Only used in www builds.
47
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.persistent.js
+1
@@ -41,6 +41,7 @@ export const enableTrainModelFix = false;
41
export const enableTrustedTypesIntegration = false;
42
export const enableNativeTargetAsInstance = false;
43
export const disableCreateFactory = false;
44
+export const disableTextareaChildren = false;
45
46
// Only used in www builds.
47
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1
@@ -41,6 +41,7 @@ export const enableTrainModelFix = false;
41
export const enableTrustedTypesIntegration = false;
42
export const enableNativeTargetAsInstance = false;
43
export const disableCreateFactory = false;
44
+export const disableTextareaChildren = false;
45
46
// Only used in www builds.
47
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1
@@ -39,6 +39,7 @@ export const enableTrainModelFix = false;
39
export const enableTrustedTypesIntegration = false;
40
export const enableNativeTargetAsInstance = false;
41
export const disableCreateFactory = false;
42
+export const disableTextareaChildren = false;
43
44
// Only used in www builds.
45
export function addUserTimingListener() {
packages/shared/forks/ReactFeatureFlags.www.js
+2
@@ -92,6 +92,8 @@ export const enableNativeTargetAsInstance = false;
92
93
export const disableCreateFactory = false;
94
95
+export const disableTextareaChildren = false;
96
+
97
// Flow magic to verify the exports of this file match the original version.
98
// eslint-disable-next-line no-unused-vars
99
type Check<_X, Y: _X, X: Y = _X> = null;