-
Notifications
You must be signed in to change notification settings - Fork 47.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ReactDOMSelect makeover, fix edge-case inconsistencies and remove state
- Loading branch information
Showing
2 changed files
with
122 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,11 +22,14 @@ var assign = require('Object.assign'); | |
|
||
var select = ReactElement.createFactory('select'); | ||
|
||
function updateWithPendingValueIfMounted() { | ||
function updateOptionsIfPendingUpdateAndMounted() { | ||
/*jshint validthis:true */ | ||
if (this.isMounted()) { | ||
this.setState({value: this._pendingValue}); | ||
this._pendingValue = 0; | ||
if (this._pendingUpdate) { | ||
this._pendingUpdate = false; | ||
var value = LinkedValueUtils.getValue(this); | ||
if (value != null && this.isMounted()) { | ||
updateOptions(this, value); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -56,40 +59,43 @@ function selectValueType(props, propName, componentName) { | |
} | ||
|
||
/** | ||
* If `value` is supplied, updates <option> elements on mount and update. | ||
* @param {ReactComponent} component Instance of ReactDOMSelect | ||
* @param {?*} propValue For uncontrolled components, null/undefined. For | ||
* controlled components, a string (or with `multiple`, a list of strings). | ||
* @param {*} propValue A stringable (with `multiple`, a list of stringables). | ||
* @private | ||
*/ | ||
function updateOptions(component, propValue) { | ||
var multiple = component.props.multiple; | ||
var value = propValue != null ? propValue : component.state.value; | ||
var options = component.getDOMNode().options; | ||
var selectedValue, i, l; | ||
if (multiple) { | ||
var options = component.getDOMNode().options; | ||
|
||
if (component.props.multiple) { | ||
selectedValue = {}; | ||
for (i = 0, l = value.length; i < l; ++i) { | ||
selectedValue['' + value[i]] = true; | ||
for (i = 0, l = propValue.length; i < l; i++) { | ||
selectedValue['' + propValue[i]] = true; | ||
} | ||
for (i = 0, l = options.length; i < l; i++) { | ||
var selected = selectedValue.hasOwnProperty(options[i].value); | ||
if (options[i].selected !== selected) { | ||
options[i].selected = selected; | ||
} | ||
} | ||
} else { | ||
selectedValue = '' + value; | ||
} | ||
for (i = 0, l = options.length; i < l; i++) { | ||
var selected = multiple ? | ||
selectedValue.hasOwnProperty(options[i].value) : | ||
options[i].value === selectedValue; | ||
|
||
if (selected !== options[i].selected) { | ||
options[i].selected = selected; | ||
// Do not set `select.value` as exact behavior isn't consistent across all | ||
// browsers for all cases. | ||
selectedValue = '' + propValue; | ||
for (i = 0, l = options.length; i < l; i++) { | ||
if (options[i].value === selectedValue) { | ||
options[i].selected = true; | ||
return; | ||
} | ||
} | ||
options[0].selected = true; | ||
} | ||
} | ||
|
||
/** | ||
* Implements a <select> native component that allows optionally setting the | ||
* props `value` and `defaultValue`. If `multiple` is false, the prop must be a | ||
* string. If `multiple` is true, the prop must be an array of strings. | ||
* stringable. If `multiple` is true, the prop must be an array of stringables. | ||
* | ||
* If `value` is not supplied (or null/undefined), user actions that change the | ||
* selected option will trigger updates to the rendered options. | ||
|
@@ -111,22 +117,6 @@ var ReactDOMSelect = ReactClass.createClass({ | |
value: selectValueType | ||
}, | ||
|
||
getInitialState: function() { | ||
return {value: this.props.defaultValue || (this.props.multiple ? [] : '')}; | ||
}, | ||
|
||
componentWillMount: function() { | ||
this._pendingValue = null; | ||
}, | ||
|
||
componentWillReceiveProps: function(nextProps) { | ||
if (!this.props.multiple && nextProps.multiple) { | ||
this.setState({value: [this.state.value]}); | ||
} else if (this.props.multiple && !nextProps.multiple) { | ||
this.setState({value: this.state.value[0]}); | ||
} | ||
}, | ||
|
||
render: function() { | ||
// Clone `this.props` so we don't mutate the input. | ||
var props = assign({}, this.props); | ||
|
@@ -137,16 +127,32 @@ var ReactDOMSelect = ReactClass.createClass({ | |
return select(props, this.props.children); | ||
}, | ||
|
||
componentWillMount: function() { | ||
this._pendingUpdate = false; | ||
}, | ||
|
||
componentDidMount: function() { | ||
updateOptions(this, LinkedValueUtils.getValue(this)); | ||
var value = LinkedValueUtils.getValue(this); | ||
if (value != null) { | ||
updateOptions(this, value); | ||
} else if (this.props.defaultValue != null) { | ||
updateOptions(this, this.props.defaultValue); | ||
} | ||
}, | ||
|
||
componentDidUpdate: function(prevProps) { | ||
var value = LinkedValueUtils.getValue(this); | ||
var prevMultiple = !!prevProps.multiple; | ||
var multiple = !!this.props.multiple; | ||
if (value != null || prevMultiple !== multiple) { | ||
if (value != null) { | ||
this._pendingUpdate = false; | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
syranide
Author
Contributor
|
||
updateOptions(this, value); | ||
} else if (!prevProps.multiple !== !this.props.multiple) { | ||
// For simplicity, reapply `defaultValue` if `multiple` is toggled. | ||
if (this.props.defaultValue != null) { | ||
updateOptions(this, this.props.defaultValue); | ||
} else { | ||
// Revert the select back to its default unselected state. | ||
updateOptions(this, this.props.multiple ? [] : ''); | ||
} | ||
} | ||
}, | ||
|
||
|
@@ -157,21 +163,8 @@ var ReactDOMSelect = ReactClass.createClass({ | |
returnValue = onChange.call(this, event); | ||
} | ||
|
||
var selectedValue; | ||
if (this.props.multiple) { | ||
selectedValue = []; | ||
var options = event.target.options; | ||
for (var i = 0, l = options.length; i < l; i++) { | ||
if (options[i].selected) { | ||
selectedValue.push(options[i].value); | ||
} | ||
} | ||
} else { | ||
selectedValue = event.target.value; | ||
} | ||
|
||
this._pendingValue = selectedValue; | ||
ReactUpdates.asap(updateWithPendingValueIfMounted, this); | ||
this._pendingUpdate = true; | ||
ReactUpdates.asap(updateOptionsIfPendingUpdateAndMounted, this); | ||
return returnValue; | ||
} | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@syranide Blast from the past. As I understand it, this flag is only used so that you can avoid calling
updateOptions
again in theasap
callback. Is this purely a performance optimization or are there other semantic DOM reasons for why this flag is needed? Do you remember?