Skip to content

Commit d04d03e

Browse files
raunofreibergnhunzaker
authored andcommitted
Fix passing symbols and functions to textarea (facebook#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
1 parent 5550ed4 commit d04d03e

File tree

2 files changed

+133
-8
lines changed

2 files changed

+133
-8
lines changed

packages/react-dom/src/__tests__/ReactDOMTextarea-test.js

+124
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ describe('ReactDOMTextarea', () => {
2020
let renderTextarea;
2121

2222
beforeEach(() => {
23+
jest.resetModules();
24+
2325
React = require('react');
2426
ReactDOM = require('react-dom');
2527
ReactDOMServer = require('react-dom/server');
@@ -423,4 +425,126 @@ describe('ReactDOMTextarea', () => {
423425
ReactDOM.unmountComponentAtNode(container);
424426
ReactDOM.render(<textarea value={undefined} />, container);
425427
});
428+
429+
describe('When given a Symbol value', () => {
430+
it('treats initial Symbol value as an empty string', () => {
431+
const container = document.createElement('div');
432+
expect(() =>
433+
ReactDOM.render(
434+
<textarea value={Symbol('foobar')} onChange={() => {}} />,
435+
container,
436+
),
437+
).toWarnDev('Invalid value for prop `value`');
438+
const node = container.firstChild;
439+
440+
expect(node.value).toBe('');
441+
});
442+
443+
it('treats initial Symbol children as an empty string', () => {
444+
const container = document.createElement('div');
445+
expect(() =>
446+
ReactDOM.render(
447+
<textarea onChange={() => {}}>{Symbol('foo')}</textarea>,
448+
container,
449+
),
450+
).toWarnDev('Use the `defaultValue` or `value` props');
451+
const node = container.firstChild;
452+
453+
expect(node.value).toBe('');
454+
});
455+
456+
it('treats updated Symbol value as an empty string', () => {
457+
const container = document.createElement('div');
458+
ReactDOM.render(<textarea value="foo" onChange={() => {}} />, container);
459+
expect(() =>
460+
ReactDOM.render(
461+
<textarea value={Symbol('foo')} onChange={() => {}} />,
462+
container,
463+
),
464+
).toWarnDev('Invalid value for prop `value`');
465+
const node = container.firstChild;
466+
467+
expect(node.value).toBe('');
468+
});
469+
470+
it('treats initial Symbol defaultValue as an empty string', () => {
471+
const container = document.createElement('div');
472+
ReactDOM.render(<textarea defaultValue={Symbol('foobar')} />, container);
473+
const node = container.firstChild;
474+
475+
// TODO: defaultValue is a reserved prop and is not validated. Check warnings when they are.
476+
expect(node.value).toBe('');
477+
});
478+
479+
it('treats updated Symbol defaultValue as an empty string', () => {
480+
const container = document.createElement('div');
481+
ReactDOM.render(<textarea defaultValue="foo" />, container);
482+
ReactDOM.render(<textarea defaultValue={Symbol('foobar')} />, container);
483+
const node = container.firstChild;
484+
485+
// TODO: defaultValue is a reserved prop and is not validated. Check warnings when they are.
486+
expect(node.value).toBe('foo');
487+
});
488+
});
489+
490+
describe('When given a function value', () => {
491+
it('treats initial function value as an empty string', () => {
492+
const container = document.createElement('div');
493+
expect(() =>
494+
ReactDOM.render(
495+
<textarea value={() => {}} onChange={() => {}} />,
496+
container,
497+
),
498+
).toWarnDev('Invalid value for prop `value`');
499+
const node = container.firstChild;
500+
501+
expect(node.value).toBe('');
502+
});
503+
504+
it('treats initial function children as an empty string', () => {
505+
const container = document.createElement('div');
506+
expect(() =>
507+
ReactDOM.render(
508+
<textarea onChange={() => {}}>{() => {}}</textarea>,
509+
container,
510+
),
511+
).toWarnDev('Use the `defaultValue` or `value` props');
512+
const node = container.firstChild;
513+
514+
expect(node.value).toBe('');
515+
});
516+
517+
it('treats updated function value as an empty string', () => {
518+
const container = document.createElement('div');
519+
ReactDOM.render(<textarea value="foo" onChange={() => {}} />, container);
520+
expect(() =>
521+
ReactDOM.render(
522+
<textarea value={() => {}} onChange={() => {}} />,
523+
container,
524+
),
525+
).toWarnDev('Invalid value for prop `value`');
526+
const node = container.firstChild;
527+
528+
expect(node.value).toBe('');
529+
});
530+
531+
it('treats initial function defaultValue as an empty string', () => {
532+
const container = document.createElement('div');
533+
ReactDOM.render(<textarea defaultValue={() => {}} />, container);
534+
const node = container.firstChild;
535+
536+
// TODO: defaultValue is a reserved prop and is not validated. Check warnings when they are.
537+
expect(node.value).toBe('');
538+
});
539+
540+
it('treats updated function defaultValue as an empty string', () => {
541+
const container = document.createElement('div');
542+
ReactDOM.render(<textarea defaultValue="foo" />, container);
543+
ReactDOM.render(<textarea defaultValue={() => {}} />, container);
544+
const node = container.firstChild;
545+
546+
// TODO: defaultValue is a reserved prop and is not validated. Check warnings when they are.
547+
expect(node.value).toBe('foo');
548+
});
549+
});
426550
});

packages/react-dom/src/client/ReactDOMFiberTextarea.js

+9-8
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ import warning from 'shared/warning';
1212

1313
import ReactControlledValuePropTypes from '../shared/ReactControlledValuePropTypes';
1414
import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCurrentFiber';
15+
import {getToStringValue, toString} from './ToStringValue';
16+
import type {ToStringValue} from './ToStringValue';
1517

1618
let didWarnValDefaultVal = false;
1719

1820
type TextAreaWithWrapperState = HTMLTextAreaElement & {
1921
_wrapperState: {
20-
initialValue: string,
22+
initialValue: ToStringValue,
2123
},
2224
};
2325

@@ -54,7 +56,7 @@ export function getHostProps(element: Element, props: Object) {
5456
...props,
5557
value: undefined,
5658
defaultValue: undefined,
57-
children: '' + node._wrapperState.initialValue,
59+
children: toString(node._wrapperState.initialValue),
5860
};
5961

6062
return hostProps;
@@ -110,7 +112,7 @@ export function initWrapperState(element: Element, props: Object) {
110112
children = children[0];
111113
}
112114

113-
defaultValue = '' + children;
115+
defaultValue = children;
114116
}
115117
if (defaultValue == null) {
116118
defaultValue = '';
@@ -119,18 +121,17 @@ export function initWrapperState(element: Element, props: Object) {
119121
}
120122

121123
node._wrapperState = {
122-
initialValue: '' + initialValue,
124+
initialValue: getToStringValue(initialValue),
123125
};
124126
}
125127

126128
export function updateWrapper(element: Element, props: Object) {
127129
const node = ((element: any): TextAreaWithWrapperState);
128-
const value = props.value;
130+
const value = getToStringValue(props.value);
129131
if (value != null) {
130132
// Cast `value` to a string to ensure the value is set correctly. While
131133
// browsers typically do this as necessary, jsdom doesn't.
132-
const newValue = '' + value;
133-
134+
const newValue = toString(value);
134135
// To avoid side effects (such as losing text selection), only set value if changed
135136
if (newValue !== node.value) {
136137
node.value = newValue;
@@ -140,7 +141,7 @@ export function updateWrapper(element: Element, props: Object) {
140141
}
141142
}
142143
if (props.defaultValue != null) {
143-
node.defaultValue = props.defaultValue;
144+
node.defaultValue = toString(getToStringValue(props.defaultValue));
144145
}
145146
}
146147

0 commit comments

Comments
 (0)