Add disable <textarea/> children flag (#17874)

This commit is contained in:
Dominic Gannaway
2020-01-20 15:12:30 +00:00
committed by GitHub
parent a209a97ed7
commit 9fd760ce75
9 changed files with 183 additions and 85 deletions
+158 -72
View File
@@ -19,6 +19,8 @@ describe('ReactDOMTextarea', () => {
let renderTextarea;
const ReactFeatureFlags = require('shared/ReactFeatureFlags');
beforeEach(() => {
jest.resetModules();
@@ -287,23 +289,58 @@ describe('ReactDOMTextarea', () => {
}
});
it('should treat children like `defaultValue`', () => {
const container = document.createElement('div');
let stub = <textarea>giraffe</textarea>;
let node;
if (ReactFeatureFlags.disableTextareaChildren) {
it('should ignore children content', () => {
const container = document.createElement('div');
let stub = <textarea>giraffe</textarea>;
let node;
expect(() => {
node = renderTextarea(stub, container);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(() => {
node = renderTextarea(stub, container);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('');
// Changing children should do nothing, it functions like `defaultValue`.
stub = ReactDOM.render(<textarea>gorilla</textarea>, container);
expect(node.value).toEqual('');
});
}
expect(node.value).toBe('giraffe');
if (ReactFeatureFlags.disableTextareaChildren) {
it('should receive defaultValue and still ignore children content', () => {
let node;
// Changing children should do nothing, it functions like `defaultValue`.
stub = ReactDOM.render(<textarea>gorilla</textarea>, container);
expect(node.value).toEqual('giraffe');
});
expect(() => {
node = renderTextarea(
<textarea defaultValue="dragon">monkey</textarea>,
);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('dragon');
});
}
if (!ReactFeatureFlags.disableTextareaChildren) {
it('should treat children like `defaultValue`', () => {
const container = document.createElement('div');
let stub = <textarea>giraffe</textarea>;
let node;
expect(() => {
node = renderTextarea(stub, container);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('giraffe');
// Changing children should do nothing, it functions like `defaultValue`.
stub = ReactDOM.render(<textarea>gorilla</textarea>, container);
expect(node.value).toEqual('giraffe');
});
}
it('should keep value when switching to uncontrolled element if not changed', () => {
const container = document.createElement('div');
@@ -342,71 +379,120 @@ describe('ReactDOMTextarea', () => {
expect(node.value).toEqual('puppies');
});
it('should allow numbers as children', () => {
let node;
expect(() => {
node = renderTextarea(<textarea>{17}</textarea>);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('17');
});
if (ReactFeatureFlags.disableTextareaChildren) {
it('should ignore numbers as children', () => {
let node;
expect(() => {
node = renderTextarea(<textarea>{17}</textarea>);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('');
});
}
it('should allow booleans as children', () => {
let node;
expect(() => {
node = renderTextarea(<textarea>{false}</textarea>);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('false');
});
if (!ReactFeatureFlags.disableTextareaChildren) {
it('should allow numbers as children', () => {
let node;
expect(() => {
node = renderTextarea(<textarea>{17}</textarea>);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('17');
});
}
it('should allow objects as children', () => {
const obj = {
toString: function() {
return 'sharkswithlasers';
},
};
let node;
expect(() => {
node = renderTextarea(<textarea>{obj}</textarea>);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('sharkswithlasers');
});
if (ReactFeatureFlags.disableTextareaChildren) {
it('should ignore booleans as children', () => {
let node;
expect(() => {
node = renderTextarea(<textarea>{false}</textarea>);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('');
});
}
it('should throw with multiple or invalid children', () => {
expect(() => {
expect(() =>
ReactTestUtils.renderIntoDocument(
<textarea>
{'hello'}
{'there'}
</textarea>,
),
).toThrow('<textarea> can only have at most one child');
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
if (!ReactFeatureFlags.disableTextareaChildren) {
it('should allow booleans as children', () => {
let node;
expect(() => {
node = renderTextarea(<textarea>{false}</textarea>);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('false');
});
}
let node;
expect(() => {
expect(
() =>
(node = renderTextarea(
if (ReactFeatureFlags.disableTextareaChildren) {
it('should ignore objects as children', () => {
const obj = {
toString: function() {
return 'sharkswithlasers';
},
};
let node;
expect(() => {
node = renderTextarea(<textarea>{obj}</textarea>);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('');
});
}
if (!ReactFeatureFlags.disableTextareaChildren) {
it('should allow objects as children', () => {
const obj = {
toString: function() {
return 'sharkswithlasers';
},
};
let node;
expect(() => {
node = renderTextarea(<textarea>{obj}</textarea>);
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('sharkswithlasers');
});
}
if (!ReactFeatureFlags.disableTextareaChildren) {
it('should throw with multiple or invalid children', () => {
expect(() => {
expect(() =>
ReactTestUtils.renderIntoDocument(
<textarea>
<strong />
{'hello'}
{'there'}
</textarea>,
)),
).not.toThrow();
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
),
).toThrow('<textarea> can only have at most one child');
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('[object Object]');
});
let node;
expect(() => {
expect(
() =>
(node = renderTextarea(
<textarea>
<strong />
</textarea>,
)),
).not.toThrow();
}).toErrorDev(
'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
);
expect(node.value).toBe('[object Object]');
});
}
it('should unmount', () => {
const container = document.createElement('div');
+15 -13
View File
@@ -14,6 +14,8 @@ import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCur
import {getToStringValue, toString} from './ToStringValue';
import type {ToStringValue} from './ToStringValue';
import {disableTextareaChildren} from 'shared/ReactFeatureFlags';
let didWarnValDefaultVal = false;
type TextAreaWithWrapperState = HTMLTextAreaElement & {|
@@ -85,9 +87,7 @@ export function initWrapperState(element: Element, props: Object) {
// Only bother fetching default value if we're going to use it
if (initialValue == null) {
let defaultValue = props.defaultValue;
// TODO (yungsters): Remove support for children content in <textarea>.
let children = props.children;
let {children, defaultValue} = props;
if (children != null) {
if (__DEV__) {
console.error(
@@ -95,19 +95,21 @@ export function initWrapperState(element: Element, props: Object) {
'children on <textarea>.',
);
}
invariant(
defaultValue == null,
'If you supply `defaultValue` on a <textarea>, do not pass children.',
);
if (Array.isArray(children)) {
if (!disableTextareaChildren) {
invariant(
children.length <= 1,
'<textarea> can only have at most one child.',
defaultValue == null,
'If you supply `defaultValue` on a <textarea>, do not pass children.',
);
children = children[0];
}
if (Array.isArray(children)) {
invariant(
children.length <= 1,
'<textarea> can only have at most one child.',
);
children = children[0];
}
defaultValue = children;
defaultValue = children;
}
}
if (defaultValue == null) {
defaultValue = '';
+3
View File
@@ -105,3 +105,6 @@ export const disableLegacyContext = false;
// Disables React.createFactory
export const disableCreateFactory = false;
// Disables children for <textarea> elements
export const disableTextareaChildren = false;
@@ -46,6 +46,7 @@ export const disableSchedulerTimeoutBasedOnReactExpirationTime = false;
export const enableTrainModelFix = false;
export const enableTrustedTypesIntegration = false;
export const disableCreateFactory = false;
export const disableTextareaChildren = false;
// Only used in www builds.
export function addUserTimingListener() {
@@ -41,6 +41,7 @@ export const enableTrainModelFix = false;
export const enableTrustedTypesIntegration = false;
export const enableNativeTargetAsInstance = false;
export const disableCreateFactory = false;
export const disableTextareaChildren = false;
// Only used in www builds.
export function addUserTimingListener() {
@@ -41,6 +41,7 @@ export const enableTrainModelFix = false;
export const enableTrustedTypesIntegration = false;
export const enableNativeTargetAsInstance = false;
export const disableCreateFactory = false;
export const disableTextareaChildren = false;
// Only used in www builds.
export function addUserTimingListener() {
@@ -41,6 +41,7 @@ export const enableTrainModelFix = false;
export const enableTrustedTypesIntegration = false;
export const enableNativeTargetAsInstance = false;
export const disableCreateFactory = false;
export const disableTextareaChildren = false;
// Only used in www builds.
export function addUserTimingListener() {
@@ -39,6 +39,7 @@ export const enableTrainModelFix = false;
export const enableTrustedTypesIntegration = false;
export const enableNativeTargetAsInstance = false;
export const disableCreateFactory = false;
export const disableTextareaChildren = false;
// Only used in www builds.
export function addUserTimingListener() {
@@ -92,6 +92,8 @@ export const enableNativeTargetAsInstance = false;
export const disableCreateFactory = false;
export const disableTextareaChildren = false;
// Flow magic to verify the exports of this file match the original version.
// eslint-disable-next-line no-unused-vars
type Check<_X, Y: _X, X: Y = _X> = null;