Reputation: 194
In the following code, the callback of the setState method does not execute, as expected, after the state is set. If you check the console you will see what I mean. The state read in console.log is not the updated one. It's always the previous one (you can check this by clicking on the "Read array" button), but according to the react docs:
The second parameter to setState() is an optional callback function that will be executed once setState is completed and the component is re-rendered. Generally we recommend using componentDidUpdate() for such logic instead.
While I understand the recommendation is to use componentDidUpdate, I'm curious as to why it doesn't work as expected or if I'm reading the documentation wrong. By "once the setState is completed" I understand that it's executed once the new state has been saved.
class StateAsync extends React.Component {
constructor(props) {
super(props)
this.state = {
items: [
{ text: "JavaScript" },
{ text: "React" },
{ text: "JSFiddle" },
{ text: "Awesome" }
],
selectedItems: []
}
this.handleSelect = this.handleSelect.bind(this);
this.test = this.test.bind(this);
}
handleSelect(e) {
let selectedId = this.state.items[e.target.id].text;
let clone = [...this.state.selectedItems];
if(e.target.checked == false) {
console.log("Removing item.");
clone = clone.filter(item => item !== selectedId);
this.setState({
selectedItems: clone
}, console.log("Remove callback result is: " + this.state.selectedItems)); //-- the callback does not execute AFTER the state is set
}else{
this.setState({
selectedItems: [...this.state.selectedItems, selectedId]
}, console.log("Add callback result is: " + this.state.selectedItems)); //-- callback gets executed before the state is set
console.log("Item added.");
}
}
test(e) {
e.preventDefault();
console.log(this.state.selectedItems);
}
render() {
return (
<div>
<span onClick={this.test} className="d-none d-md-block"><button className="btn btn-dim btn-outline-light">Read Array</button></span>
<ol>
{this.state.items.map((item,index) => (
<li key={index}>
<label>
<input type="checkbox" onChange={this.handleSelect} id={index}/>
<span>{index} - {item.text}</span>
</label>
</li>
))}
</ol>
<span id="readout">
</span>
</div>
)
}
}
ReactDOM.render(<StateAsync />, document.querySelector("#app"))
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.done {
color: rgba(0, 0, 0, 0.3);
text-decoration: line-through;
}
input {
margin-right: 5px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
Here's a codepen as well:
https://codepen.io/nicholas_cb/pen/VweXoVa?editors=0010
So, the question is does the callback in the setState method execute once the setState is completed or at the same time/before?
Upvotes: 0
Views: 376
Reputation: 12071
You are executing the console log immediately and passing its return value (which will be undefined
) as the callback.
This is why you get the current state logged and there is nothing (undefined
as callback) to be executed later with the updated state.
Instead, you will need to pass a callback function that, in turn, calls the console log:
this.setState({
selectedItems: clone
}, () => console.log("Remove callback result is: " + this.state.selectedItems));
Upvotes: 2
Reputation: 53884
The callback does execute after the state changes.
The problem that you didn't add a callback.
setState(updater, [callback])
this.setState(
{ selectedItems: clone },
// closure on this.state.selectedItems
() => console.log("Remove callback result is: " + this.state.selectedItems)
);
Upvotes: 1
Reputation: 675
That's because you are not passing a callback to the setState() method, instead you are directly passing the console.log() statement.
You need to do it this way:
this.setState({
selectedItems: clone
}, () => {
console.log("Remove callback result is: " + this.state.selectedItems
}); //-- the callback does not execute AFTER the state is set
Upvotes: 1