Skip to content

Commit 590e505

Browse files
committed
Merge pull request #1759 from spicyj/gh-1698
Use setImmediate to defer value restoration
2 parents 0a1c5da + 354fb44 commit 590e505

File tree

3 files changed

+44
-38
lines changed

3 files changed

+44
-38
lines changed

src/browser/ui/dom/components/ReactDOMInput.js

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
2525
var ReactCompositeComponent = require('ReactCompositeComponent');
2626
var ReactDOM = require('ReactDOM');
2727
var ReactMount = require('ReactMount');
28+
var ReactUpdates = require('ReactUpdates');
2829

2930
var invariant = require('invariant');
3031
var merge = require('merge');
@@ -34,6 +35,13 @@ var input = ReactDOM.input;
3435

3536
var instancesByReactID = {};
3637

38+
function forceUpdateIfMounted() {
39+
/*jshint validthis:true */
40+
if (this.isMounted()) {
41+
this.forceUpdate();
42+
}
43+
}
44+
3745
/**
3846
* Implements an <input> native component that allows setting these optional
3947
* props: `checked`, `value`, `defaultChecked`, and `defaultValue`.
@@ -58,16 +66,11 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
5866
getInitialState: function() {
5967
var defaultValue = this.props.defaultValue;
6068
return {
61-
checked: this.props.defaultChecked || false,
62-
value: defaultValue != null ? defaultValue : null
69+
initialChecked: this.props.defaultChecked || false,
70+
initialValue: defaultValue != null ? defaultValue : null
6371
};
6472
},
6573

66-
shouldComponentUpdate: function() {
67-
// Defer any updates to this component during the `onChange` handler.
68-
return !this._isChanging;
69-
},
70-
7174
render: function() {
7275
// Clone `this.props` so we don't mutate the input.
7376
var props = merge(this.props);
@@ -76,10 +79,10 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
7679
props.defaultValue = null;
7780

7881
var value = LinkedValueUtils.getValue(this);
79-
props.value = value != null ? value : this.state.value;
82+
props.value = value != null ? value : this.state.initialValue;
8083

8184
var checked = LinkedValueUtils.getChecked(this);
82-
props.checked = checked != null ? checked : this.state.checked;
85+
props.checked = checked != null ? checked : this.state.initialChecked;
8386

8487
props.onChange = this._handleChange;
8588

@@ -119,14 +122,12 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
119122
var returnValue;
120123
var onChange = LinkedValueUtils.getOnChange(this);
121124
if (onChange) {
122-
this._isChanging = true;
123125
returnValue = onChange.call(this, event);
124-
this._isChanging = false;
125126
}
126-
this.setState({
127-
checked: event.target.checked,
128-
value: event.target.value
129-
});
127+
// Here we use setImmediate to wait until all updates have propagated, which
128+
// is important when using controlled components within layers:
129+
// https://github.com/facebook/react/issues/1698
130+
ReactUpdates.setImmediate(forceUpdateIfMounted, this);
130131

131132
var name = this.props.name;
132133
if (this.props.type === 'radio' && name != null) {
@@ -164,13 +165,10 @@ var ReactDOMInput = ReactCompositeComponent.createClass({
164165
'ReactDOMInput: Unknown radio button ID %s.',
165166
otherID
166167
);
167-
// In some cases, this will actually change the `checked` state value.
168-
// In other cases, there's no change but this forces a reconcile upon
169-
// which componentDidUpdate will reset the DOM property to whatever it
170-
// should be.
171-
otherInstance.setState({
172-
checked: false
173-
});
168+
// If this is a controlled radio button group, forcing the input that
169+
// was previously checked to update will cause it to be come re-checked
170+
// as appropriate.
171+
ReactUpdates.setImmediate(forceUpdateIfMounted, otherInstance);
174172
}
175173
}
176174

src/browser/ui/dom/components/ReactDOMSelect.js

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,21 @@ var LinkedValueUtils = require('LinkedValueUtils');
2323
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
2424
var ReactCompositeComponent = require('ReactCompositeComponent');
2525
var ReactDOM = require('ReactDOM');
26+
var ReactUpdates = require('ReactUpdates');
2627

2728
var merge = require('merge');
2829

2930
// Store a reference to the <select> `ReactDOMComponent`.
3031
var select = ReactDOM.select;
3132

33+
function updateWithPendingValueIfMounted() {
34+
/*jshint validthis:true */
35+
if (this.isMounted()) {
36+
this.setState({value: this._pendingValue});
37+
this._pendingValue = 0;
38+
}
39+
}
40+
3241
/**
3342
* Validation function for `value` and `defaultValue`.
3443
* @private
@@ -114,6 +123,10 @@ var ReactDOMSelect = ReactCompositeComponent.createClass({
114123
return {value: this.props.defaultValue || (this.props.multiple ? [] : '')};
115124
},
116125

126+
componentWillMount: function() {
127+
this._pendingValue = null;
128+
},
129+
117130
componentWillReceiveProps: function(nextProps) {
118131
if (!this.props.multiple && nextProps.multiple) {
119132
this.setState({value: [this.state.value]});
@@ -122,11 +135,6 @@ var ReactDOMSelect = ReactCompositeComponent.createClass({
122135
}
123136
},
124137

125-
shouldComponentUpdate: function() {
126-
// Defer any updates to this component during the `onChange` handler.
127-
return !this._isChanging;
128-
},
129-
130138
render: function() {
131139
// Clone `this.props` so we don't mutate the input.
132140
var props = merge(this.props);
@@ -154,9 +162,7 @@ var ReactDOMSelect = ReactCompositeComponent.createClass({
154162
var returnValue;
155163
var onChange = LinkedValueUtils.getOnChange(this);
156164
if (onChange) {
157-
this._isChanging = true;
158165
returnValue = onChange.call(this, event);
159-
this._isChanging = false;
160166
}
161167

162168
var selectedValue;
@@ -172,7 +178,8 @@ var ReactDOMSelect = ReactCompositeComponent.createClass({
172178
selectedValue = event.target.value;
173179
}
174180

175-
this.setState({value: selectedValue});
181+
this._pendingValue = selectedValue;
182+
ReactUpdates.setImmediate(updateWithPendingValueIfMounted, this);
176183
return returnValue;
177184
}
178185

src/browser/ui/dom/components/ReactDOMTextarea.js

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ var LinkedValueUtils = require('LinkedValueUtils');
2424
var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
2525
var ReactCompositeComponent = require('ReactCompositeComponent');
2626
var ReactDOM = require('ReactDOM');
27+
var ReactUpdates = require('ReactUpdates');
2728

2829
var invariant = require('invariant');
2930
var merge = require('merge');
@@ -33,6 +34,13 @@ var warning = require('warning');
3334
// Store a reference to the <textarea> `ReactDOMComponent`.
3435
var textarea = ReactDOM.textarea;
3536

37+
function forceUpdateIfMounted() {
38+
/*jshint validthis:true */
39+
if (this.isMounted()) {
40+
this.forceUpdate();
41+
}
42+
}
43+
3644
/**
3745
* Implements a <textarea> native component that allows setting `value`, and
3846
* `defaultValue`. This differs from the traditional DOM API because value is
@@ -92,11 +100,6 @@ var ReactDOMTextarea = ReactCompositeComponent.createClass({
92100
};
93101
},
94102

95-
shouldComponentUpdate: function() {
96-
// Defer any updates to this component during the `onChange` handler.
97-
return !this._isChanging;
98-
},
99-
100103
render: function() {
101104
// Clone `this.props` so we don't mutate the input.
102105
var props = merge(this.props);
@@ -129,11 +132,9 @@ var ReactDOMTextarea = ReactCompositeComponent.createClass({
129132
var returnValue;
130133
var onChange = LinkedValueUtils.getOnChange(this);
131134
if (onChange) {
132-
this._isChanging = true;
133135
returnValue = onChange.call(this, event);
134-
this._isChanging = false;
135136
}
136-
this.setState({value: event.target.value});
137+
ReactUpdates.setImmediate(forceUpdateIfMounted, this);
137138
return returnValue;
138139
}
139140

0 commit comments

Comments
 (0)