Skip to content

Commit 9fd760c

Browse files
authored
Add disable <textarea/> children flag (#17874)
1 parent a209a97 commit 9fd760c

9 files changed

+186
-88
lines changed

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

Lines changed: 161 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ describe('ReactDOMTextarea', () => {
1919

2020
let renderTextarea;
2121

22+
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
23+
2224
beforeEach(() => {
2325
jest.resetModules();
2426

@@ -287,23 +289,58 @@ describe('ReactDOMTextarea', () => {
287289
}
288290
});
289291

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;
294297

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+
}
300309

301-
expect(node.value).toBe('giraffe');
310+
if (ReactFeatureFlags.disableTextareaChildren) {
311+
it('should receive defaultValue and still ignore children content', () => {
312+
let node;
302313

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+
}
307344

308345
it('should keep value when switching to uncontrolled element if not changed', () => {
309346
const container = document.createElement('div');
@@ -342,71 +379,120 @@ describe('ReactDOMTextarea', () => {
342379
expect(node.value).toEqual('puppies');
343380
});
344381

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+
}
393463

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(
399469
<textarea>
400-
<strong />
470+
{'hello'}
471+
{'there'}
401472
</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+
);
407478

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+
}
410496

411497
it('should unmount', () => {
412498
const container = document.createElement('div');

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

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCur
1414
import {getToStringValue, toString} from './ToStringValue';
1515
import type {ToStringValue} from './ToStringValue';
1616

17+
import {disableTextareaChildren} from 'shared/ReactFeatureFlags';
18+
1719
let didWarnValDefaultVal = false;
1820

1921
type TextAreaWithWrapperState = HTMLTextAreaElement & {|
@@ -85,29 +87,29 @@ export function initWrapperState(element: Element, props: Object) {
8587

8688
// Only bother fetching default value if we're going to use it
8789
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;
9191
if (children != null) {
9292
if (__DEV__) {
9393
console.error(
9494
'Use the `defaultValue` or `value` props instead of setting ' +
9595
'children on <textarea>.',
9696
);
9797
}
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) {
10399
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.',
106102
);
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;
108112
}
109-
110-
defaultValue = children;
111113
}
112114
if (defaultValue == null) {
113115
defaultValue = '';

packages/shared/ReactFeatureFlags.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,6 @@ export const disableLegacyContext = false;
105105

106106
// Disables React.createFactory
107107
export const disableCreateFactory = false;
108+
109+
// Disables children for <textarea> elements
110+
export const disableTextareaChildren = false;

packages/shared/forks/ReactFeatureFlags.native-fb.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
4646
export const enableTrainModelFix = false;
4747
export const enableTrustedTypesIntegration = false;
4848
export const disableCreateFactory = false;
49+
export const disableTextareaChildren = false;
4950

5051
// Only used in www builds.
5152
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.native-oss.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const enableTrainModelFix = false;
4141
export const enableTrustedTypesIntegration = false;
4242
export const enableNativeTargetAsInstance = false;
4343
export const disableCreateFactory = false;
44+
export const disableTextareaChildren = false;
4445

4546
// Only used in www builds.
4647
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.persistent.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const enableTrainModelFix = false;
4141
export const enableTrustedTypesIntegration = false;
4242
export const enableNativeTargetAsInstance = false;
4343
export const disableCreateFactory = false;
44+
export const disableTextareaChildren = false;
4445

4546
// Only used in www builds.
4647
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.test-renderer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export const enableTrainModelFix = false;
4141
export const enableTrustedTypesIntegration = false;
4242
export const enableNativeTargetAsInstance = false;
4343
export const disableCreateFactory = false;
44+
export const disableTextareaChildren = false;
4445

4546
// Only used in www builds.
4647
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.test-renderer.www.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export const enableTrainModelFix = false;
3939
export const enableTrustedTypesIntegration = false;
4040
export const enableNativeTargetAsInstance = false;
4141
export const disableCreateFactory = false;
42+
export const disableTextareaChildren = false;
4243

4344
// Only used in www builds.
4445
export function addUserTimingListener() {

packages/shared/forks/ReactFeatureFlags.www.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,8 @@ export const enableNativeTargetAsInstance = false;
9292

9393
export const disableCreateFactory = false;
9494

95+
export const disableTextareaChildren = false;
96+
9597
// Flow magic to verify the exports of this file match the original version.
9698
// eslint-disable-next-line no-unused-vars
9799
type Check<_X, Y: _X, X: Y = _X> = null;

0 commit comments

Comments
 (0)