I am trying to understand the underlying cause for some somewhat "magical" behavior I am seeing that I cannot fully explain, and which is not apparent from reading the ReactJS source code.
When calling the setState
method synchronously in response to an onChange
event on an input, everything works as expected. The "new" value of the input is already present, and so the DOM is not actually updated. This is highly desirable because it means the cursor will not jump to the end of the input box.
However, when running a component with exactly the same structure but that calls setState
asynchronously, the "new" value of the input does not appear to be present, causing ReactJS to actually touch the DOM, which causes the cursor to jump to the end of the input.
Apparently, something is intervening to "reset" the input back to its prior value
in the asynchronous case, which it is not doing in the synchronous case. What is this mechanic?
Synchronous Example
var synchronouslyUpdatingComponent =
React.createFactory(React.createClass({
getInitialState: function () {
return {value: "Hello"};
},
changeHandler: function (e) {
this.setState({value: e.target.value});
},
render: function () {
var valueToSet = this.state.value;
console.log("Rendering...");
console.log("Setting value:" + valueToSet);
if(this.isMounted()) {
console.log("Current value:" + this.getDOMNode().value);
}
return React.DOM.input({value: valueToSet,
onChange: this.changeHandler});
}
}));
Note that the code will log in the render
method, printing out the current value
of the actual DOM node.
When typing an "X" between the two Ls of "Hello", we see the following console output, and the cursor stays where expected:
Rendering...
Setting value:HelXlo
Current value:HelXlo
Asynchronous Example
var asynchronouslyUpdatingComponent =
React.createFactory(React.createClass({
getInitialState: function () {
return {value: "Hello"};
},
changeHandler: function (e) {
var component = this;
var value = e.target.value;
window.setTimeout(function() {
component.setState({value: value});
});
},
render: function () {
var valueToSet = this.state.value;
console.log("Rendering...");
console.log("Setting value:" + valueToSet);
if(this.isMounted()) {
console.log("Current value:" + this.getDOMNode().value);
}
return React.DOM.input({value: valueToSet,
onChange: this.changeHandler});
}
}));
This is precisely the same as the above, except that the call to setState
is in a setTimeout
callback.
In this case, typing an X between the two Ls yields the following console output, and the cursor jumps to the end of the input:
Rendering...
Setting value:HelXlo
Current value:Hello
Why is this?
I understand React's concept of a Controlled Component, and so it makes sense that user changes to the value
are ignored. But it looks like the value
is in fact changed, and then explicitly reset.
Apparently, calling setState
synchronously ensures that it takes effect before the reset, while calling setState
at any other time happens after the reset, forcing a re-render.
Is this in fact what's happening?
JS Bin Example
http://jsbin.com/sogunutoyi/1/
See Question&Answers more detail:
os