-
Notifications
You must be signed in to change notification settings - Fork 47.3k
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
Normalize onChange behavior in IE for inputs with placeholders #3826
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,7 @@ var ReactClass = require('ReactClass'); | |
var ReactElement = require('ReactElement'); | ||
var ReactMount = require('ReactMount'); | ||
var ReactUpdates = require('ReactUpdates'); | ||
var isTextInputElement = require('isTextInputElement'); | ||
|
||
var assign = require('Object.assign'); | ||
var findDOMNode = require('findDOMNode'); | ||
|
@@ -28,13 +29,21 @@ var input = ReactElement.createFactory('input'); | |
|
||
var instancesByReactID = {}; | ||
|
||
|
||
function forceUpdateIfMounted() { | ||
/*jshint validthis:true */ | ||
if (this.isMounted()) { | ||
this.forceUpdate(); | ||
} | ||
} | ||
|
||
function isIeInputEvent(event) { | ||
return ('documentMode' in document) | ||
&& document.documentMode > 9 | ||
&& event.nativeEvent.type === 'input' | ||
&& isTextInputElement(event.target); | ||
} | ||
|
||
/** | ||
* Implements an <input> native component that allows setting these optional | ||
* props: `checked`, `value`, `defaultChecked`, and `defaultValue`. | ||
|
@@ -57,8 +66,15 @@ var ReactDOMInput = ReactClass.createClass({ | |
|
||
mixins: [AutoFocusMixin, LinkedValueUtils.Mixin, ReactBrowserComponentMixin], | ||
|
||
componentWillMount: function() { | ||
var defaultValue = this.props.defaultValue; | ||
|
||
this._uncontrolledValue = defaultValue != null ? '' + defaultValue : ''; | ||
}, | ||
|
||
getInitialState: function() { | ||
var defaultValue = this.props.defaultValue; | ||
|
||
return { | ||
initialChecked: this.props.defaultChecked || false, | ||
initialValue: defaultValue != null ? defaultValue : null | ||
|
@@ -115,6 +131,24 @@ var ReactDOMInput = ReactClass.createClass({ | |
_handleChange: function(event) { | ||
var returnValue; | ||
var onChange = LinkedValueUtils.getOnChange(this.props); | ||
|
||
// IE 10+ fire input events when setting/unsetting a placeholder | ||
// we guard against it by checking if the next and | ||
// last values are both empty and bailing out of the change | ||
// https://github.com/facebook/react/issues/3484 | ||
if ( isIeInputEvent(event) ) { | ||
var controlledValue = LinkedValueUtils.getValue(this.props); | ||
var lastValue = controlledValue != null ? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Intuitively it seems like a bad idea to apply different logic depending on whether or not the component is controlled, tracking the last value and always comparing that seems like the simplest and safest approach? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. different logic meaning using props.value vs _uncontrolledValue? I thought about just always tracking a 'lastValue' but I think (still early so I may be off base) you end up with additional complexity in componentWillReceiveProps, in cases where you switch between controlled & uncontrolled. It seemed simpler to localize all the logic in one place as much as possible vs spread over more lifecycle hooks? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Urgh, there's also a question of what constitutes the "last value" from which to determine if the change event should fire I guess, the last one provided to the component or the last one in the DOM input. There's a problem in that the value of DOM inputs can and will change outside of our control/knowledge and unless I'm mistaken there is no 100% robust mechanism that allows us to know the actual last value of the DOM input. Perhaps I'm overthinking it, but it seems that either we need to know the last value of the DOM input (which isn't robust AFAIK) or we need to know the last value of the input component but that is only a partial solution (and only for controlled inputs at that). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oof I hadn't thought of that, you mean if the user provides a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
That can only be applied to controlled components, not uncontrolled. Uncontrolled components are frequently updated through directly setting the As for controlled components, don't quote me on this, but it's probably a no-go to look at the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
right, sorry I meant correct for just controlled values. On that point my initial worry seems to be partly true (did a quick fiddle), starting an input uncontrolled, and switching to controlled will confuse "this._lastValue" unless you are also watching for the switch in |
||
'' + controlledValue : | ||
this._uncontrolledValue; | ||
|
||
this._uncontrolledValue = event.target.value; | ||
|
||
if ( event.target.value === '' && lastValue === '') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: remove space after opening paren |
||
return returnValue; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems fragile considering there's lots of code beneath this that may or may not need to be executed, when all this really wants to do is discard the call to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yeah, honestly i returned |
||
} | ||
} | ||
|
||
if (onChange) { | ||
returnValue = onChange.call(this, event); | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 things here.
isIEInputEvent
&&
at the end of the previous lines and then let's wrap the whole return in parensAnd now actually a 3rd since I'm looking. We can cache the value of
'documentMode' in document && document.documentMode > 9
so that we don't need to look it up each time (any access of DOM properties has a cost).