The reason for React state updates not being reflected immediately is due to the current closure of the state variable. It is still referring to the old value. State updates require a re-render to reflect the updated value. When React re-renders the component, a new closure is created to reflect the new state updates.
In the below example, the
const Counter = () =>{
const [counter, setCounter] = useState(0);
const count = () => {
setCounter(counter + 1) //still getting the old value
console.log(counter)
}
return(
<div>
<p><button onClick={count}>Count</button></p>
</div>
)
}
The state updates provided by the useState hook's setter method are asynchronous. Therefore, the updates will not reflect immediately. But that is not the reason we don't see the changes immediately. The below example introduces a descent time delay for useState to complete the asynchronous operation. Yet we see the old state value. Because the state variable still belongs to the old closure.
const Counter = () =>{
const [count, setCount] = useState(0);
const delay = (ms: number) => new Promise(res => setTimeout(res, ms));
const incrementCount = async () => {
setCount(count + 1);
await delay(5000)
console.log(`After waiting 5 seconds: ${count}`)
}
return(
<div>
<p><button onClick={incrementCount}>Plus</button></p>
</div>
)
}
We can use the useEffect hook to reflect any state changes. useEffect accepts a function and an array of dependencies as arguments. When one of the dependencies changes, the useEffect is re-evaluated. In this case, it guarantees to re-run whenever counter changes.
useEffect(() => {
console.log(counter)
}, [counter])
If your current state depends on the previous state value, you need access to the previous state. It is bit confusing. Have a look at the below code.
const Counter = () =>{
const [count, setCount] = useState(0);
const incrementCount6 = () => {
setCount(count + 1);
setCount(count + 2);
setCount(count + 3);
}
useEffect(() => {
console.log(count)
}, [count])
return(
<div>
<p><button onClick={incrementCount6}>Count 6</button></p>
</div>
)
}
It appears that incrementCount6 updates the count by six at a time. But the reality is the count is getting updated by three on each time incrementCount6 is executed. That is, because React uses Object.assign() under the hood to batch state update statements. React sees this state update statements as:
That resolves to
const incrementCount6 = () => {
setCount((count) => count + 1);
setCount((count) => count + 2);
setCount((count) => count + 3);
}