React state not updating immediately?

You update the state and you expect it to happen immediately but it doesn’t. It might seems like the state update isn’t updating or lagging behind.

function onClick(){
	//let's say last state was 1
	setSomeState(2)
	console.log(someState); //will log 1 but not 2
}

but this is actually normal, just as expected. The component has to rerender before updating the new state.

This reminded me of when I was 9 years old, traveling abroad and I experienced thunderstorm for the first time. Late right?! Where I grew up there were no thunderstorms so naturally I was astounded that first you see the light and then hear the sound several seconds later! It was so unexpected and didn’t make any sense to me! This was somewhat similar with the React state update and I also realised it awkwardly late on the road to learn React.

Here’s another example:

function App(){

    const [count, setCount] = useState(1);

    function onClick(){
        setCount(count + 1)
        console.log(count);  
    // First time the button is clicked this will log 1, not 2
    // This console logged count will always "lag behind"
    }

    console.log(count) 
    //This will log 1 on first render 
    //before clicking the button.
    //After the first button click, the state 
    //will be updated which triggers a second 
    //render and then this will log 2

    return(
    <button onClick={onClick}>increment count</button>
    )
	
}

setCount tells React to re-render the component later with the updated value.

Let’s dive deeper into why the state doesn’t update immediately…

Plenty of articles have been written about this saying setState is asynchronous. This may sounds probable but it isn’t the exact reason why your state doesn’t update immediately.

If that was the reason you could just do

function onClick(){
	await setCount(count + 1) //not possibile
	console.log(count); //logs 2
 }

but this isn’t possbile.

The reason why the state doesn’t update immediately is because for each render, the state is immutable.

You can see that …

const [someState, setSomeState] = useState()

…are both constants values ! So they’re immutable. The state remains constant inside the render but can be changed between two renders. The same goes for dispatch and useReducer.

we could go further and ask us the question, but why are they immutable? Why isn’t it just let? There are probably plenty of reasons why the React team decided this, for performance reasons, has to work for concurrent mode etc etc… but this is as deep as this article goes.

What if you need to use the future state you just updated ?

First question to ask yourself is “can I wait for the next rerender to achieve what I want to do?”

If not, here are three solutions to this problems. First being the most preferred solution and the last one the least preferred one.

capture the new value and use it again

In most cases this solution is enough to solve your problem. Let’s take an example where you’d want to fetch data based on the value you’ve just updated.

//Instead of this:

function onChange(){
  setCount(count + 1);
  getData(count); 
  //here count is not equal to the state 
  //you just set, it's the previous state
}

//Do this:
function onChange(){
  const newCount = count + 1;
  setCount(newCount);
  getData(newCount); 
  //now you can be sure you call getData
  //with the state you just set
}

make useEffect listen to the state changes

Use useEffect sparingly. If you want something to happen after user’s action, you most probably don’t need useEffect because then you can update the state in the action handler (onClick, onChangeValue etc…) like in the example above. But if there’s no user’s action, you can make useEffect listen to the state changes, like this:

useEffect(() => {
  getData(count);
},[count])

Avoiding useEffect when possible can save you from unnecessary complications and could avoid unexpected behaviour:

useRef

Think twice before to be sure you need to use a ref. On the rare occasions when the above solutions are not enough you could use useRef. Updates to useRef happen synchronously. But be aware that ref updates don’t trigger a rerender!


const countRef = useRef()

useEffect(() => {
  countRef.current = countRef.current + 1;
  getData(countRef.current);
}, [])

So next time when you have a state that doesn’t update immediately, you’ll know why and what to do!

Feel free to share if you see somebody that has this problem. Don’t hesitate to be in touch on twitter, especially if this article leaves you with some questions!


Get more tips in my almost monthly newsletter about CSS & React!