Skip to content

Commit

Permalink
Add disable <textarea/> children flag (#17874)
Browse files Browse the repository at this point in the history
  • Loading branch information
trueadm committed Jan 20, 2020
1 parent a209a97 commit 9fd760c
Show file tree
Hide file tree
Showing 9 changed files with 186 additions and 88 deletions.
236 changes: 161 additions & 75 deletions packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ describe('ReactDOMTextarea', () => {

let renderTextarea;

const ReactFeatureFlags = require('shared/ReactFeatureFlags');

beforeEach(() => {
jest.resetModules();

Expand Down Expand Up @@ -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');
Expand Down Expand Up @@ -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');
});

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');
});

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');
});

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 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('');
});
}

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');
});
}

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('');
});
}

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');
});
}

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');
});
}

let node;
expect(() => {
expect(
() =>
(node = renderTextarea(
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');
Expand Down
28 changes: 15 additions & 13 deletions packages/react-dom/src/client/ReactDOMTextarea.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 & {|
Expand Down Expand Up @@ -85,29 +87,29 @@ 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(
'Use the `defaultValue` or `value` props instead of setting ' +
'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 = '';
Expand Down
3 changes: 3 additions & 0 deletions packages/shared/ReactFeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,6 @@ export const disableLegacyContext = false;

// Disables React.createFactory
export const disableCreateFactory = false;

// Disables children for <textarea> elements
export const disableTextareaChildren = false;
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-fb.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.native-oss.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.persistent.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions packages/shared/forks/ReactFeatureFlags.test-renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
2 changes: 2 additions & 0 deletions packages/shared/forks/ReactFeatureFlags.www.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 9fd760c

Please sign in to comment.