Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix passing symbols and functions to textarea #13362

Merged
merged 11 commits into from
Aug 14, 2018
124 changes: 124 additions & 0 deletions packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ describe('ReactDOMTextarea', () => {
let renderTextarea;

beforeEach(() => {
jest.resetModules();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any particular reason for this addition? I think the only reason we do that is when we have tests that require multiple separate versions of React at the same time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Running the tests added in this PR were failing unless I ran resetModules before each.

image

Although running them 1 by 1 via fit seemed to fix the issue.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, right, makes sense. Otherwise the warning will only pop up once 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Btw, thanks for the feedback. I really appreciate it 🙂


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
Original file line number Diff line number Diff line change
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