Skip to content

Commit 0df1709

Browse files
committed
Remove loose check when assigning non-number inputs
This commit removes a check I added when working on number input issues where we perform a loose check on an input's value before we assign it. This prevented controlled text inputs from disallowing numeric text entry. I also added a DOM fixture text case. Related issues: #9561 (comment)
1 parent 4d81744 commit 0df1709

File tree

6 files changed

+178
-56
lines changed

6 files changed

+178
-56
lines changed
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
const React = window.React;
2+
3+
import Fixture from '../../Fixture';
4+
5+
class InputTestCase extends React.Component {
6+
static defaultProps = {
7+
type: 'text',
8+
defaultValue: '',
9+
parseAs: 'text'
10+
}
11+
12+
constructor () {
13+
super(...arguments);
14+
15+
this.state = {
16+
value: this.props.defaultValue
17+
};
18+
}
19+
20+
onChange = (event) => {
21+
const raw = event.target.value;
22+
23+
switch (this.props.type) {
24+
case 'number':
25+
const parsed = parseFloat(event.target.value, 10);
26+
27+
this.setState({ value: isNaN(parsed) ? '' : parsed });
28+
29+
break;
30+
default:
31+
this.setState({ value: raw });
32+
}
33+
}
34+
35+
render() {
36+
const { children, type, defaultValue } = this.props;
37+
const { value } = this.state;
38+
39+
return (
40+
<Fixture>
41+
<div>{children}</div>
42+
43+
<div className="control-box">
44+
<fieldset>
45+
<legend>Controlled {type}</legend>
46+
<input type={type} value={value} onChange={this.onChange} />
47+
<p className="hint">
48+
Value: {JSON.stringify(this.state.value)}
49+
</p>
50+
</fieldset>
51+
52+
<fieldset>
53+
<legend>Uncontrolled {type}</legend>
54+
<input type={type} defaultValue={defaultValue} />
55+
</fieldset>
56+
</div>
57+
</Fixture>
58+
);
59+
}
60+
}
61+
62+
export default InputTestCase;

fixtures/dom/src/components/fixtures/text-inputs/index.js

Lines changed: 53 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,64 @@
11
const React = window.React;
22

3-
class TextInputFixtures extends React.Component {
4-
state = {
5-
color: '#ffaaee',
6-
};
3+
import Fixture from '../../Fixture';
4+
import FixtureSet from '../../FixtureSet';
5+
import TestCase from '../../TestCase';
6+
import InputTestCase from './InputTestCase';
77

8-
renderControlled = (type) => {
9-
let id = `controlled_${type}`;
8+
class TextInputFixtures extends React.Component {
9+
render() {
10+
return (
11+
<FixtureSet
12+
title="Inputs"
13+
description="Common behavior across controled and uncontrolled inputs"
14+
>
15+
<TestCase title="Numbers in a controlled text field with no handler">
16+
<TestCase.Steps>
17+
<li>Move the cursor to after "2" in the text field</li>
18+
<li>Type ".2" into the text input</li>
19+
</TestCase.Steps>
1020

11-
let onChange = e => {
12-
let value = e.target.value;
13-
if (type === 'number') {
14-
value = value === '' ? '' : parseFloat(value, 10) || 0;
15-
}
16-
this.setState({
17-
[type] : value,
18-
});
19-
};
21+
<TestCase.ExpectedResult>
22+
The text field's value should not update.
23+
</TestCase.ExpectedResult>
2024

21-
let state = this.state[type] || '';
25+
<Fixture>
26+
<div className="control-box">
27+
<fieldset>
28+
<legend>Value as number</legend>
29+
<input value={2} onChange={() => {}} />
30+
</fieldset>
2231

23-
return (
24-
<div key={type} className="field">
25-
<label className="control-label" htmlFor={id}>{type}</label>
26-
<input id={id} type={type} value={state} onChange={onChange} />
27-
&nbsp; &rarr; {JSON.stringify(state)}
28-
</div>
29-
);
30-
}
32+
<fieldset>
33+
<legend>Value as string</legend>
34+
<input value={"2"} onChange={() => {}} />
35+
</fieldset>
36+
</div>
37+
</Fixture>
3138

32-
renderUncontrolled = (type) => {
33-
let id = `uncontrolled_${type}`;
34-
return (
35-
<div key={type} className="field">
36-
<label className="control-label" htmlFor={id}>{type}</label>
37-
<input id={id} type={type} />
38-
</div>
39-
);
40-
}
39+
<p className="footnote">
40+
This issue was first introduced when we added extra logic
41+
to number inputs to prevent unexpected behavior in Chrome
42+
and Safari (see the number input test case).
43+
</p>
44+
</TestCase>
4145

42-
render() {
43-
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input
44-
let types = [
45-
'text', 'email', 'number', 'url', 'tel',
46-
'color', 'date', 'datetime-local',
47-
'time', 'month', 'week', 'range', 'password',
48-
];
49-
return (
50-
<form onSubmit={event => event.preventDefault()}>
51-
<fieldset>
52-
<legend>Controlled</legend>
53-
{types.map(this.renderControlled)}
54-
</fieldset>
55-
<fieldset>
56-
<legend>Uncontrolled</legend>
57-
{types.map(this.renderUncontrolled)}
58-
</fieldset>
59-
</form>
46+
<TestCase title="All inputs" description="General test of all inputs">
47+
<InputTestCase type="text" defaultValue="Text" />
48+
<InputTestCase type="email" defaultValue="[email protected]"/>
49+
<InputTestCase type="number" defaultValue={0} />
50+
<InputTestCase type="url" defaultValue="http://example.com"/>
51+
<InputTestCase type="tel" defaultValue="555-555-5555"/>
52+
<InputTestCase type="color" defaultValue="#ff0000" />
53+
<InputTestCase type="date" defaultValue="2017-01-01" />
54+
<InputTestCase type="datetime-local" defaultValue="2017-01-01T01:00" />
55+
<InputTestCase type="time" defaultValue="01:00" />
56+
<InputTestCase type="month" defaultValue="2017-01" />
57+
<InputTestCase type="week" defaultValue="2017-W01" />
58+
<InputTestCase type="range" defaultValue={0.5} />
59+
<InputTestCase type="password" defaultValue="" />
60+
</TestCase>
61+
</FixtureSet>
6062
);
6163
}
6264
}

scripts/fiber/tests-passing.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1452,6 +1452,9 @@ src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js
14521452
* should properly control a value even if no event listener exists
14531453
* should control a value in reentrant events
14541454
* should control values in reentrant events with different targets
1455+
* does change the number 2 to "2.0" with no change handler
1456+
* does change the string "2" to "2.0" with no change handler
1457+
* changes the number 2 to "2.0" using a change handler
14551458
* should display `defaultValue` of number 0
14561459
* only assigns defaultValue if it changes
14571460
* should display "true" for `defaultValue` of `true`
@@ -1671,6 +1674,7 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
16711674
* can deprioritize a tree from without dropping work
16721675
* can resume work in a subtree even when a parent bails out
16731676
* can resume work in a bailed subtree within one pass
1677+
* can resume mounting a class component
16741678
* can reuse work done after being preempted
16751679
* can reuse work that began but did not complete, after being preempted
16761680
* can reuse work if shouldComponentUpdate is false, after being preempted

src/renderers/dom/fiber/wrappers/ReactDOMFiberInput.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ var ReactDOMInput = {
210210
node.value = '' + value;
211211
}
212212
// eslint-disable-next-line
213-
} else if (value != node.value) {
213+
} else {
214214
// Cast `value` to a string to ensure the value is set correctly. While
215215
// browsers typically do this as necessary, jsdom doesn't.
216216
node.value = '' + value;

src/renderers/dom/shared/wrappers/__tests__/ReactDOMInput-test.js

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,60 @@ describe('ReactDOMInput', () => {
179179
document.body.removeChild(container);
180180
});
181181

182+
describe('switching text inputs between numeric and string numbers', () => {
183+
it('does change the number 2 to "2.0" with no change handler', () => {
184+
var stub = <input type="text" value={2} onChange={jest.fn()} />;
185+
stub = ReactTestUtils.renderIntoDocument(stub);
186+
var node = ReactDOM.findDOMNode(stub);
187+
188+
node.value = '2.0';
189+
190+
ReactTestUtils.Simulate.change(stub);
191+
192+
expect(node.getAttribute('value')).toBe('2');
193+
expect(node.value).toBe('2');
194+
});
195+
196+
it('does change the string "2" to "2.0" with no change handler', () => {
197+
var stub = <input type="text" value={'2'} onChange={jest.fn()} />;
198+
stub = ReactTestUtils.renderIntoDocument(stub);
199+
var node = ReactDOM.findDOMNode(stub);
200+
201+
node.value = '2.0';
202+
203+
ReactTestUtils.Simulate.change(stub);
204+
205+
expect(node.getAttribute('value')).toBe('2');
206+
expect(node.value).toBe('2');
207+
});
208+
209+
it('changes the number 2 to "2.0" using a change handler', () => {
210+
class Stub extends React.Component {
211+
state = {
212+
value: 2,
213+
};
214+
onChange = event => {
215+
this.setState({value: event.target.value});
216+
};
217+
render() {
218+
const {value} = this.state;
219+
220+
return <input type="text" value={value} onChange={this.onChange} />;
221+
}
222+
}
223+
224+
var stub = ReactTestUtils.renderIntoDocument(<Stub />);
225+
var node = ReactDOM.findDOMNode(stub);
226+
227+
node.value = '2.0';
228+
229+
ReactTestUtils.Simulate.change(node);
230+
231+
expect(node.getAttribute('value')).toBe('2.0');
232+
expect(node.value).toBe('2.0');
233+
});
234+
});
235+
182236
it('should display `defaultValue` of number 0', () => {
183237
var stub = <input type="text" defaultValue={0} />;
184238
stub = ReactTestUtils.renderIntoDocument(stub);
@@ -411,10 +465,10 @@ describe('ReactDOMInput', () => {
411465
});
412466

413467
ReactDOM.render(<input value="a" />, container);
414-
expect(nodeValueSetter.mock.calls.length).toBe(0);
468+
expect(nodeValueSetter.mock.calls.length).toBe(1);
415469

416470
ReactDOM.render(<input value="b" />, container);
417-
expect(nodeValueSetter.mock.calls.length).toBe(1);
471+
expect(nodeValueSetter.mock.calls.length).toBe(2);
418472
});
419473

420474
it('should properly control a value of number `0`', () => {
@@ -434,7 +488,7 @@ describe('ReactDOMInput', () => {
434488

435489
node.value = '0.0';
436490
ReactTestUtils.Simulate.change(node, {target: {value: '0.0'}});
437-
expect(node.value).toBe('0.0');
491+
expect(node.value).toBe('0');
438492
});
439493

440494
it('should properly control 0.0 for a number input', () => {

src/renderers/dom/stack/client/wrappers/ReactDOMInput.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ var ReactDOMInput = {
202202
node.value = '' + value;
203203
}
204204
// eslint-disable-next-line
205-
} else if (value != node.value) {
205+
} else {
206206
// Cast `value` to a string to ensure the value is set correctly. While
207207
// browsers typically do this as necessary, jsdom doesn't.
208208
node.value = '' + value;

0 commit comments

Comments
 (0)