Jay Jay - 1 month ago 7
React JSX Question

setState(..., callback) calls callback before state has been set

I commonly write code like this (for re-use):

function doSomething() {
// I do something with this.state.MyVar
}

function buttonClicked(e) {
this.setState({MyVar: e.target.value}, this.doSomething());
}

function otherEvent(e) {
this.setState({MyVar: e.target.value}, this.doSomething());
}


Once doSomething() is called, it is not uncommon for the value to be different from that of e.target.value. It seems as though the callback does not actually occur after the state has been updated?

Note, this always works:

function doSomething(value) {
// I do something with value
}

function buttonClicked(e) {
this.setState({MyVar: e.target.value});
this.doSomething(e.target.value);
}


This is in Google Chrome, and with latest version of ReactJS.

Answer

Omit the parentheses as they immediately execute the function and pass the resolved value (return value) as the callback - which is not desired. The callback must be a function reference not invocation, because the callback is internally invoked. Using parentheses, and thus consequently invoking the function it will give the illusion of executing immediately:

this.setState({MyVar: e.target.value}, doSomething);
//                                                ^^ No parens!

This way, doSomething's reference is passed instead of an invocation, and it will execute accordingly.


Examples of these kinds of problems can be seen in many cases. Consider this snippet:

document.getElementById("test").addEventListener("click", foo()); //to demonstrate the common mistake, let's try with parentheses

function foo() {
  console.log("I've been clicked (but not really!)"); 
}
<button id="test">Click me!</button>

You can see that we are adding a listener to button #test, and it seems that we are setting foo as the event handler, but in actuality it executes immediately, even though we have not clicked it! This is because when the JavaScript is run, foo() is immediately executed, thus logging I've been clicked (but not really!) before it should. Since the function returns nothing, it's equivalent to:

document.getElementById("test").addEventListener("click", undefined);

undefined is passed as the event handler because the resolved value is passed as the event handler, and since the function returned nothing, it was undefined.

This can be applied here because when you pass a invocation for a callback, you are actually passing the return value of that function, because you invoke it. To combat this you need to pass a reference.