Fix Chrome number input backspace and invalid input issue #7359

Merged
merged 31 commits into from Mar 27, 2017

Conversation

@nhunzaker
Collaborator

nhunzaker commented Jul 27, 2016

In Google Chrome, backspacing an controlled and uncontrolled number inputs causes an unexpected behavior:

backspace

More cases (and what fixes look like) here: #7253 (comment)

Issues with uncontrolled inputs

This is because assigning defaultValue causes side-effects on value. It seems to trigger validation on value as if it were directly assigned itself.

This Codepen provides a step by step reproduction case:

http://codepen.io/nhunzaker/pen/zBaokp

wat

This PR wraps the defaultValue assignment in a conditional so that it will only ever be assigned if the defaultValue has changed.

Issues with controlled inputs

It looks like the issue here is that value is being assigned using the standard React method of updating attributes when the props get passed in here:

https://github.com/facebook/react/blob/master/src/renderers/dom/shared/ReactDOMComponent.js#L926

There is some logic in ReactDOMInput to prevent duplicate values from being assigned here:

https://github.com/facebook/react/blob/master/src/renderers/dom/client/wrappers/ReactDOMInput.js#L201-L210

Unfortunately the new value has already been assigned.

This PR adds a check for the value prop, and only assigns it if is different. It never assigns the value attribute if it hasn't changed:

I don't really know why this works. I didn't think node.setAttribute('value', value) was meaningful. What do you think?


Related to #7253

+ } else if (attributeName === 'value') {
+ if (node.value != value) {
+ node.value = '' + value;
+ }

This comment has been minimized.

@zpao

zpao Jul 28, 2016

Member

Ironically our code used to look a lot like this… https://github.com/facebook/react/blob/0.14-stable/src/renderers/dom/shared/DOMPropertyOperations.js#L187-L197

@jimfb - I think this is more fallout from your changes. Can you take a look and figure out a way forward?

@zpao

zpao Jul 28, 2016

Member

Ironically our code used to look a lot like this… https://github.com/facebook/react/blob/0.14-stable/src/renderers/dom/shared/DOMPropertyOperations.js#L187-L197

@jimfb - I think this is more fallout from your changes. Can you take a look and figure out a way forward?

This comment has been minimized.

@nhunzaker

nhunzaker Jul 29, 2016

Collaborator

Ack! I crossed the streams!

This PR was supposed to be solely for fixing defaultValue, but it looks I got my branches mixed up and pushed the more general fix for both inputs. I actually just replied here:

#7253 (comment)

Again, pushing to this branch was a total accident. A bunch of tests fail now. I'll work on getting those to pass in the mean time, but could definitely use some advice.

@nhunzaker

nhunzaker Jul 29, 2016

Collaborator

Ack! I crossed the streams!

This PR was supposed to be solely for fixing defaultValue, but it looks I got my branches mixed up and pushed the more general fix for both inputs. I actually just replied here:

#7253 (comment)

Again, pushing to this branch was a total accident. A bunch of tests fail now. I'll work on getting those to pass in the mean time, but could definitely use some advice.

@ghost ghost added the CLA Signed label Jul 28, 2016

@nhunzaker

This comment has been minimized.

Show comment
Hide comment
@nhunzaker

nhunzaker Jul 29, 2016

Collaborator

@zpao Thanks for the historical insight. All tests pass. I believe I've caught every edge case. I'll enumerate them in #7253. I'm happy to add back the HAS_SIDE_EFFECTS constant, however this seems to be truly specific to the value attribute. What do you think?

Collaborator

nhunzaker commented Jul 29, 2016

@zpao Thanks for the historical insight. All tests pass. I believe I've caught every edge case. I'll enumerate them in #7253. I'm happy to add back the HAS_SIDE_EFFECTS constant, however this seems to be truly specific to the value attribute. What do you think?

@nhunzaker nhunzaker changed the title from Fix Chrome uncontrolled number input backspace issue. Only re-assign defaultValue if it is different to Fix Chrome number input backspace and invalid input issue Jul 29, 2016

@ghost ghost added the CLA Signed label Jul 29, 2016

@nhunzaker

This comment has been minimized.

Show comment
Hide comment
@nhunzaker

nhunzaker Jul 29, 2016

Collaborator

Cases enumerated here:

#7253 (comment)

Collaborator

nhunzaker commented Jul 29, 2016

Cases enumerated here:

#7253 (comment)

@nhunzaker

This comment has been minimized.

Show comment
Hide comment
@nhunzaker

nhunzaker Jul 29, 2016

Collaborator

Ah and it looks like lint is going to fail because my solution relies on loose type coercion. Hmm. Do I have permission to ignore these rules?

Collaborator

nhunzaker commented Jul 29, 2016

Ah and it looks like lint is going to fail because my solution relies on loose type coercion. Hmm. Do I have permission to ignore these rules?

@@ -158,6 +158,10 @@ var DOMPropertyOperations = {
} else if (propertyInfo.hasBooleanValue ||
(propertyInfo.hasOverloadedBooleanValue && value === true)) {
node.setAttribute(attributeName, '');
+ } else if (attributeName === 'value' && node.hasAttribute('value')) {

This comment has been minimized.

@nhunzaker

nhunzaker Jul 29, 2016

Collaborator

Type coercion solved my problems, however it makes the initial case hard. Empty inputs are always equal to 0.

I think this could be better. Any suggestions?

@nhunzaker

nhunzaker Jul 29, 2016

Collaborator

Type coercion solved my problems, however it makes the initial case hard. Empty inputs are always equal to 0.

I think this could be better. Any suggestions?

@nhunzaker

This comment has been minimized.

Show comment
Hide comment
@nhunzaker

nhunzaker Aug 1, 2016

Collaborator

Added ignore statements for ESLint rules and a test for setAttributeForProperty.

Collaborator

nhunzaker commented Aug 1, 2016

Added ignore statements for ESLint rules and a test for setAttributeForProperty.

+ // Use loose coercion to prevent replacement on comparisons like
+ // '3e1' == 30 in Chrome (~52).
+ if (node.value != value) { // eslint-disable-line
+ node.setAttribute(attributeName, '' + value);

This comment has been minimized.

@nhunzaker

nhunzaker Aug 1, 2016

Collaborator

One thing I'm not clear on yet: Why assign the value attribute at all?.

ReactDOMInput will assign the value property in updateWrapper. We could do that in mountWrapper as well. Is it important that the attribute be assigned?

@nhunzaker

nhunzaker Aug 1, 2016

Collaborator

One thing I'm not clear on yet: Why assign the value attribute at all?.

ReactDOMInput will assign the value property in updateWrapper. We could do that in mountWrapper as well. Is it important that the attribute be assigned?

This comment has been minimized.

@aweary

aweary Sep 4, 2016

Member

So, there's a difference between setting node.value and calling node.setAttribute('value'). node.value tracks the value of the node in JS, and setAttribute will actually set the attribute on the element in the DOM.

This change causes an issue where the DOM attribute is never updated, because node.value is already updated prior to this check, so it never actually sets the attribute.

@aweary

aweary Sep 4, 2016

Member

So, there's a difference between setting node.value and calling node.setAttribute('value'). node.value tracks the value of the node in JS, and setAttribute will actually set the attribute on the element in the DOM.

This change causes an issue where the DOM attribute is never updated, because node.value is already updated prior to this check, so it never actually sets the attribute.

This comment has been minimized.

@nhunzaker

nhunzaker Sep 4, 2016

Collaborator

Hmm. So actually: should the value attribute update for anything other than checkbox and radio?

Following browser behavior, editing the text inside of a text input doesn't update the value attribute.

Possible my current code doesn't do this, but to me that's the correct functionality.

@nhunzaker

nhunzaker Sep 4, 2016

Collaborator

Hmm. So actually: should the value attribute update for anything other than checkbox and radio?

Following browser behavior, editing the text inside of a text input doesn't update the value attribute.

Possible my current code doesn't do this, but to me that's the correct functionality.

This comment has been minimized.

@aweary

aweary Sep 4, 2016

Member

React does deviate from browser behavior when it comes to controlled components (e.g., change events on keydown instead of on blur). Currently the value attribute does update on controlled inputs, and it looks like we're explicitly setting it, so there may be a good reason I'm not aware of.

cc @spicyj @zpao

@aweary

aweary Sep 4, 2016

Member

React does deviate from browser behavior when it comes to controlled components (e.g., change events on keydown instead of on blur). Currently the value attribute does update on controlled inputs, and it looks like we're explicitly setting it, so there may be a good reason I'm not aware of.

cc @spicyj @zpao

This comment has been minimized.

@nhunzaker

nhunzaker Sep 4, 2016

Collaborator

There is an extra complication here... Assigning the value attribute forces validation for number inputs. Check this out:

control

Notice how the decimal gets stripped? My fix focuses on avoiding touching the input as much as possible to avoid unexpected behavior like this. Fortunately, I believe this also conforms to the standard way inputs work.

@nhunzaker

nhunzaker Sep 4, 2016

Collaborator

There is an extra complication here... Assigning the value attribute forces validation for number inputs. Check this out:

control

Notice how the decimal gets stripped? My fix focuses on avoiding touching the input as much as possible to avoid unexpected behavior like this. Fortunately, I believe this also conforms to the standard way inputs work.

This comment has been minimized.

@sophiebits

sophiebits Sep 4, 2016

Member

I believe the current behavior of setting the attribute is intentional though I could be misremembering. I know we definitely need to set value on the <progress> element. You can look at the history of ReactDOMInput to see when jimfb last made changes.

@sophiebits

sophiebits Sep 4, 2016

Member

I believe the current behavior of setting the attribute is intentional though I could be misremembering. I know we definitely need to set value on the <progress> element. You can look at the history of ReactDOMInput to see when jimfb last made changes.

This comment has been minimized.

@nhunzaker

nhunzaker Sep 5, 2016

Collaborator

Looks like this is taken care of, because node.value on progress is never assigned, so it's out of sync with props.value, and it reassigns the attribute.

progress

@nhunzaker

nhunzaker Sep 5, 2016

Collaborator

Looks like this is taken care of, because node.value on progress is never assigned, so it's out of sync with props.value, and it reassigns the attribute.

progress

@STRML

This comment has been minimized.

Show comment
Hide comment
@STRML

STRML Aug 24, 2016

Contributor

Anything this is waiting on? This is a pretty obnoxious bug. Thanks so much to @nhunzaker for putting this PR together.

Contributor

STRML commented Aug 24, 2016

Anything this is waiting on? This is a pretty obnoxious bug. Thanks so much to @nhunzaker for putting this PR together.

@ghost ghost added the CLA Signed label Aug 24, 2016

@aweary

This comment has been minimized.

Show comment
Hide comment
@aweary

aweary Aug 24, 2016

Member

I'll try and get this reviewed when I get back from vacation next week, if no one else gets a chance to do it in the meantime. Overall it looks good to me though!

Member

aweary commented Aug 24, 2016

I'll try and get this reviewed when I get back from vacation next week, if no one else gets a chance to do it in the meantime. Overall it looks good to me though!

@nhunzaker

This comment has been minimized.

Show comment
Hide comment
@nhunzaker

nhunzaker Aug 25, 2016

Collaborator

@aweary No worries -- I'm on vacation too! Looking forward to getting this one fix in...

Collaborator

nhunzaker commented Aug 25, 2016

@aweary No worries -- I'm on vacation too! Looking forward to getting this one fix in...

@ghost ghost added the CLA Signed label Aug 25, 2016

@nhunzaker

This comment has been minimized.

Show comment
Hide comment
@nhunzaker

nhunzaker Sep 1, 2016

Collaborator

@aweary Upstreamed with master. Anything I can do to help make this smoother to review?

Collaborator

nhunzaker commented Sep 1, 2016

@aweary Upstreamed with master. Anything I can do to help make this smoother to review?

@ghost ghost added the CLA Signed label Sep 1, 2016

@aweary

This comment has been minimized.

Show comment
Hide comment
@aweary

aweary Sep 1, 2016

Member

@nhunzaker thanks for doing that. Today is my first day back from vacation so I'll try to review it soon. I just want to pull this down and step through the code path with a few examples to make sure this makes sense.

This seems to be an area where we've had a lot of issues in the past, so I just want to be extra careful in avoiding any regressions.

Member

aweary commented Sep 1, 2016

@nhunzaker thanks for doing that. Today is my first day back from vacation so I'll try to review it soon. I just want to pull this down and step through the code path with a few examples to make sure this makes sense.

This seems to be an area where we've had a lot of issues in the past, so I just want to be extra careful in avoiding any regressions.

@ghost ghost added the CLA Signed label Sep 1, 2016

@nhunzaker

This comment has been minimized.

Show comment
Hide comment
@nhunzaker

nhunzaker Sep 1, 2016

Collaborator

@aweary Yep, I totally understand. This is a complicated issue.

It would be cool if we could build up a test suite for browser quirks. I started working on one here as I worked through these input issues:

https://github.com/nhunzaker/react-issue-reproducer

Hardly satisfactory, and I'm not suggesting you all do anything with it, but I'd love to see browser coverage for inputs, particularly for the change event behavior and side-effect property assignments. So many input issues have test cases written up as jsfiddles, it seems like you could collect them and write webdriver scripts to reproduce the issue.

Collaborator

nhunzaker commented Sep 1, 2016

@aweary Yep, I totally understand. This is a complicated issue.

It would be cool if we could build up a test suite for browser quirks. I started working on one here as I worked through these input issues:

https://github.com/nhunzaker/react-issue-reproducer

Hardly satisfactory, and I'm not suggesting you all do anything with it, but I'd love to see browser coverage for inputs, particularly for the change event behavior and side-effect property assignments. So many input issues have test cases written up as jsfiddles, it seems like you could collect them and write webdriver scripts to reproduce the issue.

@ghost ghost added the CLA Signed label Sep 1, 2016

@aweary

This comment has been minimized.

Show comment
Hide comment
@aweary

aweary Sep 4, 2016

Member

Had some time to review, good work overall, but this introduces an issue where the value attribute is never updated in the DOM (see my line comment). I'm pretty sure we still want the value attribute to stay in sync with node.value so we'll have to address that somehow.

Member

aweary commented Sep 4, 2016

Had some time to review, good work overall, but this introduces an issue where the value attribute is never updated in the DOM (see my line comment). I'm pretty sure we still want the value attribute to stay in sync with node.value so we'll have to address that somehow.

@ghost ghost added CLA Signed labels Sep 4, 2016

@coveralls

This comment has been minimized.

Show comment
Hide comment
@coveralls

coveralls Sep 5, 2016

Coverage Status

Coverage increased (+0.02%) to 87.215% when pulling 3fe4dfa on nhunzaker:nh-chrome-backspace into 6a525fd on facebook:master.

Coverage Status

Coverage increased (+0.02%) to 87.215% when pulling 3fe4dfa on nhunzaker:nh-chrome-backspace into 6a525fd on facebook:master.

@nhunzaker

This comment has been minimized.

Show comment
Hide comment
@nhunzaker

nhunzaker Sep 5, 2016

Collaborator

Pushed an update.

I didn't like how there was an exception just for value in setValueForProperty, so I moved it into a mutation method. I also added a few more tests and caught an edge cause where an empty value switches to zero and the attribute isn't updated (because of the loose check).

Pushing the commit also murdered our conversation thread... So just to clarify:

All non-input elements always get a fresh value attribute when the prop changes

For example, a <progress> element will always get an attribute assignment

<progress value={value} max="1" />

progress

All non-text input elements always get a fresh value attribute when the prop changes.

Like changing a radio's value will always reflect in the DOM:

<input type="radio" value={value}  />

radio

Text input elements only assign the value attribute once, when the input is created.

<input type="number" value={value}  />

They also only assign a new value property if it is loosely different. This prevents manipulation of the input that would expand 3e1 into 30, or have 3. drop the decimal place and set the input value to 3. This makes number inputs useable:

number

Collaborator

nhunzaker commented Sep 5, 2016

Pushed an update.

I didn't like how there was an exception just for value in setValueForProperty, so I moved it into a mutation method. I also added a few more tests and caught an edge cause where an empty value switches to zero and the attribute isn't updated (because of the loose check).

Pushing the commit also murdered our conversation thread... So just to clarify:

All non-input elements always get a fresh value attribute when the prop changes

For example, a <progress> element will always get an attribute assignment

<progress value={value} max="1" />

progress

All non-text input elements always get a fresh value attribute when the prop changes.

Like changing a radio's value will always reflect in the DOM:

<input type="radio" value={value}  />

radio

Text input elements only assign the value attribute once, when the input is created.

<input type="number" value={value}  />

They also only assign a new value property if it is loosely different. This prevents manipulation of the input that would expand 3e1 into 30, or have 3. drop the decimal place and set the input value to 3. This makes number inputs useable:

number

@coveralls

This comment has been minimized.

Show comment
Hide comment
@coveralls

coveralls Sep 5, 2016

Coverage Status

Coverage increased (+0.02%) to 87.215% when pulling 2fdcbee on nhunzaker:nh-chrome-backspace into 6a525fd on facebook:master.

Coverage Status

Coverage increased (+0.02%) to 87.215% when pulling 2fdcbee on nhunzaker:nh-chrome-backspace into 6a525fd on facebook:master.

@coveralls

This comment has been minimized.

Show comment
Hide comment
@coveralls

coveralls Sep 5, 2016

Coverage Status

Coverage increased (+0.03%) to 87.218% when pulling d900f57 on nhunzaker:nh-chrome-backspace into 6a525fd on facebook:master.

Coverage Status

Coverage increased (+0.03%) to 87.218% when pulling d900f57 on nhunzaker:nh-chrome-backspace into 6a525fd on facebook:master.

@aweary

This comment has been minimized.

Show comment
Hide comment
@aweary

aweary Sep 5, 2016

Member

cc @jimfb, are there good reasons for updating the value attribute on text inputs for controlled components?

Member

aweary commented Sep 5, 2016

cc @jimfb, are there good reasons for updating the value attribute on text inputs for controlled components?

nhunzaker added some commits Dec 15, 2016

Add number input browser test fixtures
During the course of the number input fix, we uncovered many edge
cases. This commit adds browser test fixtures for each of those instances.
Address edge case preventing number precision lower than 1 place
0.0 coerces to 0, however they are not the same value when doing
string comparision. This prevented controlled number inputs from
inputing the characters `0.00`.

Also adds test cases.
Accommodate lack of IE9 number input support
IE9 does not support number inputs. Number inputs in IE9 fallback to
traditional text inputs. This means that accessing `input.value` will
report the raw text, rather than parsing a numeric value.

This commit makes the ReactDOMInput wrapper check to see if the `type`
prop has been configured to `"number"`. In those cases, it will
perform a comparison based upon `parseFloat` instead of the raw input
value.
Remove footnotes about IE exponent issues
With the recent IE9 fix, IE properly inserts `e` when it produces an
invalid number.
Address exception in IE9/10 ChangeEventPlugin blur event
On blur, inputs have their values assigned. This is so that number
inputs do not conduct unexpected behavior in
Chrome/Safari. Unfortunately, there are cases where the target
instance might be undefined in IE9/10, raising an exception.
@nhunzaker

This comment has been minimized.

Show comment
Hide comment
@nhunzaker

nhunzaker Mar 25, 2017

Collaborator

Rebased with master. I also added @iangilman's case for trailing dashes, and a test case for negative numbers:

dashes

Collaborator

nhunzaker commented Mar 25, 2017

Rebased with master. I also added @iangilman's case for trailing dashes, and a test case for negative numbers:

dashes

@jquense

This comment has been minimized.

Show comment
Hide comment
@jquense

jquense Mar 25, 2017

Collaborator

I'm interested in why 3- needs to be supported (if only to make sure react-widgets does it)

Collaborator

jquense commented Mar 25, 2017

I'm interested in why 3- needs to be supported (if only to make sure react-widgets does it)

@nhunzaker

This comment has been minimized.

Show comment
Hide comment
@nhunzaker

nhunzaker Mar 25, 2017

Collaborator

@jquense You know what, I think I have this wrong. The test case is when you select the entire number value and - to start typing a new number. Stable react sees - as an invalid number and wipes away the value.

I'll update the test case. I also need to figure out what to do about prettier formatting. prettier wants to put my ESLint ignore comments on a new line, which causes linting to fail.

Away I go.

Collaborator

nhunzaker commented Mar 25, 2017

@jquense You know what, I think I have this wrong. The test case is when you select the entire number value and - to start typing a new number. Stable react sees - as an invalid number and wipes away the value.

I'll update the test case. I also need to figure out what to do about prettier formatting. prettier wants to put my ESLint ignore comments on a new line, which causes linting to fail.

Away I go.

@jquense

This comment has been minimized.

Show comment
Hide comment
@jquense

jquense Mar 25, 2017

Collaborator

ah gotcha. If it helps btw please feel free to look at https://github.com/jquense/react-widgets/blob/v4/packages/react-widgets/src/NumberInput.js to see how i fumbled through on this logic

Collaborator

jquense commented Mar 25, 2017

ah gotcha. If it helps btw please feel free to look at https://github.com/jquense/react-widgets/blob/v4/packages/react-widgets/src/NumberInput.js to see how i fumbled through on this logic

@nhunzaker

This comment has been minimized.

Show comment
Hide comment
@nhunzaker

nhunzaker Mar 25, 2017

Collaborator

Cool. I just had to update the test case, everything works fine. If the number isn't valid, it parses to a blank value, which is also what the number input reports, so no change occurs.

I've also run prettier on this branch and dealt with the associated eslint issue.

Collaborator

nhunzaker commented Mar 25, 2017

Cool. I just had to update the test case, everything works fine. If the number isn't valid, it parses to a blank value, which is also what the number input reports, so no change occurs.

I've also run prettier on this branch and dealt with the associated eslint issue.

@nhunzaker

This comment has been minimized.

Show comment
Hide comment
@nhunzaker

nhunzaker Mar 25, 2017

Collaborator

@jquense This NumberInput is cool. How crazy would it be to bake something like this into React?

Later... later.. I want to get this PR merged so badly, haha.

Collaborator

nhunzaker commented Mar 25, 2017

@jquense This NumberInput is cool. How crazy would it be to bake something like this into React?

Later... later.. I want to get this PR merged so badly, haha.

@aweary

aweary approved these changes Mar 27, 2017

Later... later.. I want to get this PR merged so badly, haha.

Oh yeah? well, how about we go ahead and do that then 😄 I was able to test this out today using the DOM fixtures. I verified everything seems to be working correctly in:

  • IE9
  • IE10
  • IE11
  • Edge 14
  • Chrome 57 (Windows)
  • Chrome 57 (macOS)
  • Chrome (Android, Galaxy S5)
  • Firefox 52 (Windows)
  • Firefox 52 (macOS)
  • Opera 44 (Windows)
  • Safari 10 (macOS)
  • Safari (iOS 9)
  • Safari (iOS 8)

Everything appears to be working great, and I see nothing immediately wrong with the changes made. So, LGTM!

@aweary aweary merged commit 29d9710 into facebook:master Mar 27, 2017

1 check passed

ci/circleci Your tests passed on CircleCI!
Details

@aweary aweary added this to the 15-hipri milestone Mar 27, 2017

bvaughn added a commit that referenced this pull request Mar 29, 2017

Fix Chrome number input backspace and invalid input issue (#7359)
* Only re-assign defaultValue if it is different

* Do not set value if it is the same

* Properly cover defaultValue

* Use coercion to be smart about value assignment

* Add explanation of loose type checks in value assignment.

* Add test coverage for setAttribute update.

* Only apply loose value check to text inputs

* Fix case where empty switches to zero

* Handle zero case in controlled input

* Correct mistake with default value assignment after rebase

* Do not assign bad input to number input

* Only trigger number input value attribute updates on blur

* Remove reference to LinkedValueUtils

* Record new fiber tests

* Add tests for blurred number input behavior

* Replace onBlur wrapper with rule in ChangeEventPlugin

* Sift down to only number inputs

* Re-record fiber tests

* Add test case for updating attribute on uncontrolled inputs. Make related correction

* Handle uncontrolled inputs, integrate fiber

* Reorder boolean to mitigate DOM checks

* Only assign value if it is different

* Add number input browser test fixtures

During the course of the number input fix, we uncovered many edge
cases. This commit adds browser test fixtures for each of those instances.

* Address edge case preventing number precision lower than 1 place

0.0 coerces to 0, however they are not the same value when doing
string comparision. This prevented controlled number inputs from
inputing the characters `0.00`.

Also adds test cases.

* Accommodate lack of IE9 number input support

IE9 does not support number inputs. Number inputs in IE9 fallback to
traditional text inputs. This means that accessing `input.value` will
report the raw text, rather than parsing a numeric value.

This commit makes the ReactDOMInput wrapper check to see if the `type`
prop has been configured to `"number"`. In those cases, it will
perform a comparison based upon `parseFloat` instead of the raw input
value.

* Remove footnotes about IE exponent issues

With the recent IE9 fix, IE properly inserts `e` when it produces an
invalid number.

* Address exception in IE9/10 ChangeEventPlugin blur event

On blur, inputs have their values assigned. This is so that number
inputs do not conduct unexpected behavior in
Chrome/Safari. Unfortunately, there are cases where the target
instance might be undefined in IE9/10, raising an exception.

* Migrate over ReactDOMInput.js number input fixes to Fiber

Also re-record tests

* Update number fixtures to use latest components

* Add number input test case for dashes and negative numbers

* Replace trailing dash test case with replace with dash

Also run prettier
+ // If controlled, assign the value attribute to the current value on blur
+ let value = '' + node.value;
+ if (node.getAttribute('value') !== value) {
+ node.setAttribute('value', value);

This comment has been minimized.

@sebmarkbage

sebmarkbage Mar 30, 2017

Member

Sorry, I'm not caught up on the entire history here.

Why is this special case needed? extractEvents should not have side-effects in it.

Can this be handled by the normal controlledness flow in updateWrapper?

@sebmarkbage

sebmarkbage Mar 30, 2017

Member

Sorry, I'm not caught up on the entire history here.

Why is this special case needed? extractEvents should not have side-effects in it.

Can this be handled by the normal controlledness flow in updateWrapper?

This comment has been minimized.

@sebmarkbage

sebmarkbage Mar 30, 2017

Member

Would it be enough to flag this with enqueueStateRestore so that the normal updateWrapper kicks in at the end of the blur event?

@sebmarkbage

sebmarkbage Mar 30, 2017

Member

Would it be enough to flag this with enqueueStateRestore so that the normal updateWrapper kicks in at the end of the blur event?

This comment has been minimized.

@nhunzaker

nhunzaker Mar 30, 2017

Collaborator

I can verify. As for history:

React controlled inputs sync up the value attribute with the value property. This prevents state mismatches with form.reset() and browser plugins (though i never got a reply on which ones). Unfortunately, on number inputs in Chrome and Safari, this forces a check on the input, removing trailing decimal places, invalid characters, etc.

The only way i could get around this (i tried for months, off and on) was to defer setting the value attribute on blur.

I haven't done much with enqueueStateRestore. I'll give it a go.

@nhunzaker

nhunzaker Mar 30, 2017

Collaborator

I can verify. As for history:

React controlled inputs sync up the value attribute with the value property. This prevents state mismatches with form.reset() and browser plugins (though i never got a reply on which ones). Unfortunately, on number inputs in Chrome and Safari, this forces a check on the input, removing trailing decimal places, invalid characters, etc.

The only way i could get around this (i tried for months, off and on) was to defer setting the value attribute on blur.

I haven't done much with enqueueStateRestore. I'll give it a go.

This comment has been minimized.

@sebmarkbage

sebmarkbage Mar 30, 2017

Member

IMO we should just not set the value attribute. People writing manual DOM operations don't do that and it works fine for them, so not sure why we do.

Would just avoiding setting the attribute all together fix the problem?

@sebmarkbage

sebmarkbage Mar 30, 2017

Member

IMO we should just not set the value attribute. People writing manual DOM operations don't do that and it works fine for them, so not sure why we do.

Would just avoiding setting the attribute all together fix the problem?

This comment has been minimized.

@nhunzaker

nhunzaker Mar 30, 2017

Collaborator

That would be lovely. The only edge case is form.reset(). Honestly, I'd be in favor of just adding a warning about this, or enqueing updateWrapper on the reset form event if it can be done.

@nhunzaker

nhunzaker Mar 30, 2017

Collaborator

That would be lovely. The only edge case is form.reset(). Honestly, I'd be in favor of just adding a warning about this, or enqueing updateWrapper on the reset form event if it can be done.

This comment has been minimized.

@aweary

aweary Mar 30, 2017

Member

We also need to be clear that form.reset is totally fine when the form contains only uncontrolled inputs. If we did go the route of warning it should only warn if the form contains one or more controlled inputs.

@aweary

aweary Mar 30, 2017

Member

We also need to be clear that form.reset is totally fine when the form contains only uncontrolled inputs. If we did go the route of warning it should only warn if the form contains one or more controlled inputs.

This comment has been minimized.

@sophiebits

sophiebits Mar 30, 2017

Member

I think just .reset() and CSS input[value=foo] selectors. TBH the latter was a pretty common feature request so I'd be sad to see it go.

@sophiebits

sophiebits Mar 30, 2017

Member

I think just .reset() and CSS input[value=foo] selectors. TBH the latter was a pretty common feature request so I'd be sad to see it go.

This comment has been minimized.

@sebmarkbage

sebmarkbage Mar 30, 2017

Member

There are other attributes that matter. Such as all of those that don't have corresponding properties (generally things outside the HTML spec). There are edge cases that need removing attributes (like progress). However, for value (or checked) I don't believe there were any cases that needed it outside of intuition. E.g. the attribute shows up in DOM dev tools in Chrome etc.

However, there is nothing that relies on it being there because all those thousands of websites that do manual DOM management. Nobody manually calls setAttribute in those cases. However, as we've seen, there are cases where setting it all the time is unusual such as in this case. So it leads to undiscovered browser deviations.

That's why I think our design should be to model what a user would write manually - even if that seems unintuitive of how people think React works.

@sebmarkbage

sebmarkbage Mar 30, 2017

Member

There are other attributes that matter. Such as all of those that don't have corresponding properties (generally things outside the HTML spec). There are edge cases that need removing attributes (like progress). However, for value (or checked) I don't believe there were any cases that needed it outside of intuition. E.g. the attribute shows up in DOM dev tools in Chrome etc.

However, there is nothing that relies on it being there because all those thousands of websites that do manual DOM management. Nobody manually calls setAttribute in those cases. However, as we've seen, there are cases where setting it all the time is unusual such as in this case. So it leads to undiscovered browser deviations.

That's why I think our design should be to model what a user would write manually - even if that seems unintuitive of how people think React works.

This comment has been minimized.

@nhunzaker

nhunzaker Mar 31, 2017

Collaborator

In most cases I can think of with manual manipulation it's actually undesirable to set the value attribute because you want to rely on form.reset. at least, this has always been the case for me. Being able to style text inputs based on a dynamic value attribute is a privilege react provides, one I would be comfortable with no longer having.

React doing Dom manipulation, the correct way, is the abstraction I've always had. I think this is a very strong metaphor that makes it easier to reason about what react is doing. We deviate from that with controlled inputs. I'm in favor of this change, which moves closer towards that idea.

So I agree, armchair theory aside 😂. I can start scaffolding out what that would look like, but I won't be able to pick it up until Monday, if someone else wants to give it a shot.

@nhunzaker

nhunzaker Mar 31, 2017

Collaborator

In most cases I can think of with manual manipulation it's actually undesirable to set the value attribute because you want to rely on form.reset. at least, this has always been the case for me. Being able to style text inputs based on a dynamic value attribute is a privilege react provides, one I would be comfortable with no longer having.

React doing Dom manipulation, the correct way, is the abstraction I've always had. I think this is a very strong metaphor that makes it easier to reason about what react is doing. We deviate from that with controlled inputs. I'm in favor of this change, which moves closer towards that idea.

So I agree, armchair theory aside 😂. I can start scaffolding out what that would look like, but I won't be able to pick it up until Monday, if someone else wants to give it a shot.

This comment has been minimized.

@nhunzaker

nhunzaker Jul 11, 2017

Collaborator

Oof. I finally did it. Please see:

#10150

@nhunzaker

nhunzaker Jul 11, 2017

Collaborator

Oof. I finally did it. Please see:

#10150

@btoueg

This comment has been minimized.

Show comment
Hide comment
@btoueg

btoueg Apr 12, 2017

I've bumped my project to 15.5.4 and the issue is still there.

btoueg commented Apr 12, 2017

I've bumped my project to 15.5.4 and the issue is still there.

@gaearon

This comment has been minimized.

Show comment
Hide comment
@gaearon

gaearon Apr 12, 2017

Member

@btoueg Provide a reproducing case please?

Member

gaearon commented Apr 12, 2017

@btoueg Provide a reproducing case please?

@btoueg

This comment has been minimized.

Show comment
Hide comment
@btoueg

btoueg Jul 12, 2017

Sorry for the delay. My report was a false negative. Works fine now, congrats on fixing the issue.

btoueg commented Jul 12, 2017

Sorry for the delay. My report was a false negative. Works fine now, congrats on fixing the issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment