Fix passing symbols and functions to textarea (#13362)

* refactor: move getSafeValue to separate file

* fix(?): ReactDOMFiberTextarea sanitization for symbols and functions

* tests: add TODOs for warnings

* fix: restore accidentally removed test

* fix: remove redundant logic for initialValue

* refactor: integrate SafeValue typings into textarea

* fix: restore stringified newValue for equality check

* fix: remove getSafeValue from hostProps

* refactor: SafeValue -> ToStringValue

* refactor: update TODO comment in test file

* refactor: no need to convert children to ToStringValue
This commit is contained in:
Rauno Freiberg
2018-08-14 07:16:48 -07:00
committed by Nathan Hunzaker
parent 5550ed4a8f
commit d04d03e470
2 changed files with 133 additions and 8 deletions
@@ -20,6 +20,8 @@ describe('ReactDOMTextarea', () => {
let renderTextarea;
beforeEach(() => {
jest.resetModules();
React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
@@ -423,4 +425,126 @@ describe('ReactDOMTextarea', () => {
ReactDOM.unmountComponentAtNode(container);
ReactDOM.render(<textarea value={undefined} />, container);
});
describe('When given a Symbol value', () => {
it('treats initial Symbol value as an empty string', () => {
const container = document.createElement('div');
expect(() =>
ReactDOM.render(
<textarea value={Symbol('foobar')} onChange={() => {}} />,
container,
),
).toWarnDev('Invalid value for prop `value`');
const node = container.firstChild;
expect(node.value).toBe('');
});
it('treats initial Symbol children as an empty string', () => {
const container = document.createElement('div');
expect(() =>
ReactDOM.render(
<textarea onChange={() => {}}>{Symbol('foo')}</textarea>,
container,
),
).toWarnDev('Use the `defaultValue` or `value` props');
const node = container.firstChild;
expect(node.value).toBe('');
});
it('treats updated Symbol value as an empty string', () => {
const container = document.createElement('div');
ReactDOM.render(<textarea value="foo" onChange={() => {}} />, container);
expect(() =>
ReactDOM.render(
<textarea value={Symbol('foo')} onChange={() => {}} />,
container,
),
).toWarnDev('Invalid value for prop `value`');
const node = container.firstChild;
expect(node.value).toBe('');
});
it('treats initial Symbol defaultValue as an empty string', () => {
const container = document.createElement('div');
ReactDOM.render(<textarea defaultValue={Symbol('foobar')} />, container);
const node = container.firstChild;
// TODO: defaultValue is a reserved prop and is not validated. Check warnings when they are.
expect(node.value).toBe('');
});
it('treats updated Symbol defaultValue as an empty string', () => {
const container = document.createElement('div');
ReactDOM.render(<textarea defaultValue="foo" />, container);
ReactDOM.render(<textarea defaultValue={Symbol('foobar')} />, container);
const node = container.firstChild;
// TODO: defaultValue is a reserved prop and is not validated. Check warnings when they are.
expect(node.value).toBe('foo');
});
});
describe('When given a function value', () => {
it('treats initial function value as an empty string', () => {
const container = document.createElement('div');
expect(() =>
ReactDOM.render(
<textarea value={() => {}} onChange={() => {}} />,
container,
),
).toWarnDev('Invalid value for prop `value`');
const node = container.firstChild;
expect(node.value).toBe('');
});
it('treats initial function children as an empty string', () => {
const container = document.createElement('div');
expect(() =>
ReactDOM.render(
<textarea onChange={() => {}}>{() => {}}</textarea>,
container,
),
).toWarnDev('Use the `defaultValue` or `value` props');
const node = container.firstChild;
expect(node.value).toBe('');
});
it('treats updated function value as an empty string', () => {
const container = document.createElement('div');
ReactDOM.render(<textarea value="foo" onChange={() => {}} />, container);
expect(() =>
ReactDOM.render(
<textarea value={() => {}} onChange={() => {}} />,
container,
),
).toWarnDev('Invalid value for prop `value`');
const node = container.firstChild;
expect(node.value).toBe('');
});
it('treats initial function defaultValue as an empty string', () => {
const container = document.createElement('div');
ReactDOM.render(<textarea defaultValue={() => {}} />, container);
const node = container.firstChild;
// TODO: defaultValue is a reserved prop and is not validated. Check warnings when they are.
expect(node.value).toBe('');
});
it('treats updated function defaultValue as an empty string', () => {
const container = document.createElement('div');
ReactDOM.render(<textarea defaultValue="foo" />, container);
ReactDOM.render(<textarea defaultValue={() => {}} />, container);
const node = container.firstChild;
// TODO: defaultValue is a reserved prop and is not validated. Check warnings when they are.
expect(node.value).toBe('foo');
});
});
});
+9 -8
View File
@@ -12,12 +12,14 @@ import warning from 'shared/warning';
import ReactControlledValuePropTypes from '../shared/ReactControlledValuePropTypes';
import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCurrentFiber';
import {getToStringValue, toString} from './ToStringValue';
import type {ToStringValue} from './ToStringValue';
let didWarnValDefaultVal = false;
type TextAreaWithWrapperState = HTMLTextAreaElement & {
_wrapperState: {
initialValue: string,
initialValue: ToStringValue,
},
};
@@ -54,7 +56,7 @@ export function getHostProps(element: Element, props: Object) {
...props,
value: undefined,
defaultValue: undefined,
children: '' + node._wrapperState.initialValue,
children: toString(node._wrapperState.initialValue),
};
return hostProps;
@@ -110,7 +112,7 @@ export function initWrapperState(element: Element, props: Object) {
children = children[0];
}
defaultValue = '' + children;
defaultValue = children;
}
if (defaultValue == null) {
defaultValue = '';
@@ -119,18 +121,17 @@ export function initWrapperState(element: Element, props: Object) {
}
node._wrapperState = {
initialValue: '' + initialValue,
initialValue: getToStringValue(initialValue),
};
}
export function updateWrapper(element: Element, props: Object) {
const node = ((element: any): TextAreaWithWrapperState);
const value = props.value;
const value = getToStringValue(props.value);
if (value != null) {
// Cast `value` to a string to ensure the value is set correctly. While
// browsers typically do this as necessary, jsdom doesn't.
const newValue = '' + value;
const newValue = toString(value);
// To avoid side effects (such as losing text selection), only set value if changed
if (newValue !== node.value) {
node.value = newValue;
@@ -140,7 +141,7 @@ export function updateWrapper(element: Element, props: Object) {
}
}
if (props.defaultValue != null) {
node.defaultValue = props.defaultValue;
node.defaultValue = toString(getToStringValue(props.defaultValue));
}
}