As our cohort started learning about React, one of the first concepts we learned is the use of state and props. Without any prior knowledge, it was definitely mind boggling in the beginning. Of course it started sinking in after multiple lab practices. Hopefully you’ll find this blog helpful in making better sense of it.
State vs Props
Before we move forward, I wanted to differentiate State and Props because honestly it was the first thing I had trouble understanding.
State is data maintained inside a specific component while Props is data passed in from a parent component.
The main difference is which component owns that data. Props are read only and can only be updated by the child component IF a callback function is also passed down as props. The callback function will then trigger the upstream update of the parent’s component state.
Now let’s talk about the function that will update the component’s state.
TLDR
setState()
is asynchronous- pass a function instead of an object if you are doing some kind of calculations based on the actual current state
setState()
can accept a callback function
setState()
Using setState()
is basically making an appointment to update the component’s state. I say appointment, because setState()
is asynchronous. It means calling setState()
will not update EXACTLY after you call it. Look at the example code below :
import React, { Component } from 'react';
class App extends Component {
state = { count : 0 }
handleClick = () => {
this.setState({count: this.state.count + 1 })
this.setState({count: this.state.count + 1 })
this.setState({count: this.state.count + 1 })
console.log(this.state.count)
// this.state.count logs 0 in the first click
// this.state.count logs 1 after the second click
}
render() {
return (
<div>
<button onClick={this.handleClick}>Add 3</button>
</div>
);
}
}
export default App
You might expect that after clicking the button, handleClick
will update the state and then console.log
3, but in fact you will get 0. Also clicking the button the second time will console.log
1 Why is this happening? Is it ignoring the other 2 setState()
calls? It is not.
React intentionally waits and batches setState()
calls for better performance. The main reason behind this is because when a component’s state is altered, the component re-renders in response. This is important, let’s say a click of a button calls a setState()
to a child component AND a setState()
to it’s parent component, you don’t want the child to re-render twice. As your app becomes more complex, it can be very expensive and in turn might cause your browser to become unresponsive.
prevState()
Keeping in mind of its asynchronous nature, this makes this.state
unreliable. Going back to our previous example, each setState()
call looks at this.state before it even gets updated. if you are updating state with values that depend on the current state pass a function()
in setState()
instead of an object.
Tweaking our code from above, look at the code below:
import React, { Component } from 'react';
class App extends Component {
state = { count : 0 }
handleClick = () => {
this.setState(prevState => {
return {count: prevState.count + 1 }})
this.setState(prevState => {
return {count: prevState.count + 1 }})
this.setState(prevState => {
return {count: prevState.count + 1 }})
console.log(this.state.count)
// this.state.count logs 0 in the first click
// this.state.count logs 3 after the second click
}
render() {
return (
<div>
<button onClick={this.handleClick}>Add 3</button>
</div>
);
}
}
export default App
Clicking the button the first time will still console.log
0 but clicking it again the second time and the component re-renders, it will log 3. You can also use this if you’re updating an existing array or object in state by using the spread operator like so :
state = { arr : [{obj1}, {obj2}, {obj3}] }
handleClick = () => {
this.setState(prevState => {
return {array: [...prevState.arr, {newObj}]}
})
}
sidenote:
- The function above takes the the most updated
this.state.arr
- Uses the
...
spread operator to make a copy of that arr- Adds a new object
- Updates state using
setState()
The benefit of using a function instead of an object gives us access to the most updated state and will queue the setState() calls so that they run in order.
Callback()
Now you’re wondering, we’re still getting “0” after our first click.
Last but not least, how do you access the updated state after setState()
actually does what it’s supposed to do? setState()
can take a callback function.
Last tweak at the function below:
state = { count : 0 }
handleClick = () => {
this.setState(prevState => {
return {count: prevState.count + 3 }}, () => {
console.log(this.state.count)
// this.state.count logs 3
})
console.log(this.state.count)
//this.state.count logs 0
}
The second console.log is outside the scope therefore running before setState()
finishes setting the state, which is why it logs 0.
Basically the callback function runs AFTER setState()
happens, therefore in this scope giving you access to the most updated state.
Conclusion
I hope this somewhat made things clearer. Thank you so much for reading and please reach out to me if you any comments or suggestions.
Top comments (1)
Can you tell me any benefits of using class-based components (the only place you would use setState) since the release of 16.8 hooks?
Some comments have been hidden by the post's author - find out more