What’s with functions inside setState?
Know the difference between passing value versus passing function.
Let’s get straight to the point here and ask two questions, why do we have an option to pass a function to setState, and how does passing a function solve that?
setState is asynchronous
If you don’t know already, setState() is actually asynchronous, this is so that React could bunch up multiple setState() calls into one re-render to boost performance. Imagine having to re-render each time only one component calls setState(). That would be incredibly slow!
This is also the reason why we don’t have access to the new state yet when we try to reference it right after calling setState().
const [ count, setCount ] = useState(3)const onClick = () => {
setCount(5)
console.log(count) // first click will print out 3
}return(<button onClick={onClick}> hello </button>)
The problem
But what if the next state depends on the current state. Let’s think of a simple counter that we can increment through a button click:
const [ count, setCount ] = useState(0)
const onClick = () => {
setCount(count + 1)
}
return(
<div>
<span> { count } </span>
<button onClick={onClick}> increment! </button>
</div>
)
Every time we click the button, we will set our count to increment using setCount(), which will then signal React to re-render this component.
Now what if the user has many tabs and applications open or that they could click so fast that React will batch together our setCount() calls, the thing is, state does not update until the batch is finished.
So if we imagine two setCount() calls getting batched, it would be something like this:
// let's say count is 5
setCount(count + 1) // count is 5, becomes 6
setCount(count + 1) // count is still 5, becomes 6// re-renders, count becomes 6
we could see that even though we called setCount() to increment our count twice, it only incremented once because they got batched together and that second increment gets lost.
The Solution
So in comes functions to save the day. Let’s grab the previous scenario above but instead pass a function.
// let's say count is 5
setCount(current => current + 1) // current is 5, becomes 6
setCount(current => current + 1) // current is 6, becomes 7// re-renders, count becomes 7
And there we go, passing a function allows us to properly increment our state even when React batches our setState() calls.
Now a counter is a very simple example, but try thinking about large applications that have multiple states in different components, React has to batch setState() calls to keep our application smooth and reactive.
More depth
The problem is a mix of closures and React not updating our state instantly until the batch is finished. Because if we pass a value to React, it won’t know how that value is calculated, only the result. So let’s take the previous scenario again where we pass a value:
// let's say count is 5
setCount(count + 1) --> setCount(6)
setCount(count + 1) --> setCount(6)// re-renders, count becomes 6
So what we tell React is just to set our count to 6. Now let’s see what that looks like when we pass a function instead
// let's say count is 5
setCount(current => current + 1) --> setCount(5 => 5 + 1)
setCount(current => current + 1) --> setCount(6 => 6 + 1)// re-renders, count becomes 7