mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Add disable <textarea/> children flag (#17874)
This commit is contained in:
+158
-72
@@ -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
@@ -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 = '';
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user