Home > References > Web Development > Reactjs references

React useState set method does not reflect the change immediately

Last updated : June 5, 2022

1. Overview

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 console.log(counter) refers to the counter value before update. Because the old closure still holds the original value. When React re-renders the component, a new closure is created with the updated state value. This cycle happens for every state update.

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>
  )
}

2. Are useState state updates asynchronous?

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>
  )
}

3. How to use useState updated state value?

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])

4. How to use the previous state to update the current state in useState?

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:

Object.assign({count:1}, {count:2}, {count:3})

That resolves to {count:3} hence we see the counter increments only by three. The solution is to pass a function instead of an object to the state updater.

const incrementCount6 = () => {
    setCount((count) => count + 1);
    setCount((count) => count + 2);
    setCount((count) => count + 3);
}
L Raney
By: L Raney
Lance is a software engineer with over 15 years of experience in full-stack software development.
Read more...

Comments are disabled

No Comments