Skip to content

Commit

Permalink
Fix passing symbols and functions to textarea (#13362)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
raunofreiberg authored and nhunzaker committed Aug 14, 2018
1 parent 5550ed4 commit d04d03e
Show file tree
Hide file tree
Showing 2 changed files with 133 additions and 8 deletions.
124 changes: 124 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
Expand Up @@ -20,6 +20,8 @@ describe('ReactDOMTextarea', () => {
let renderTextarea;

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

React = require('react');
ReactDOM = require('react-dom');
ReactDOMServer = require('react-dom/server');
Expand Down Expand Up @@ -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');
});
});
});
17 changes: 9 additions & 8 deletions packages/react-dom/src/client/ReactDOMFiberTextarea.js
Expand Up @@ -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,
},
};

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -110,7 +112,7 @@ export function initWrapperState(element: Element, props: Object) {
children = children[0];
}

defaultValue = '' + children;
defaultValue = children;
}
if (defaultValue == null) {
defaultValue = '';
Expand All @@ -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;
Expand All @@ -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));
}
}

Expand Down

0 comments on commit d04d03e

Please sign in to comment.