Are you using useState the right way?

Aditya Narayan Gantayat
CodeX
Published in
4 min readNov 13, 2023

useState, the React hook is one of the routine stuffs for a React developer. We, as React devs, know that it is used to trigger a re-render whenever the state changes. It’s simple, right? I mean, what could be complicated about that? Well, I’m sure I’m not the only one who has experienced some strange behaviours with the same.

For example, lets check this code snippet out:

Simple, right? On click of the button, we basically want to change the count by one every second. To keep a track of the renders, we have used a ref.

But will that perform as expected?

After clicking of Start button.

So, we click the start button and surprisingly or not surprisingly(for those who are aware xD), it does behave weird. The count gets updated from 0 to 1 and re-renders one time due to the change in state….and that’s it!

Something wrong with the interval? Naayyy, for confirmation, check the console below! Poor guy has been logging to confirm the running of timer for 123 seconds. So, our interval is definitely working the way it should!

Something wrong with the code? Definitely! But what exactly? Its the way we are updating the state, i.e. setCount(count+1). You might argue that there’s nothing wrong with that syntactically. Yeah, nothing wrong syntactically. But to understand the problem with this implementation, we need to understand closures. The interval is created once and it executes every second(1000ms as we provided) and within the interval, we are updating the Count. The point to be highlighted is that the interval is created once i.e. on click of the start button. So closures, as we all know is a copy of the lexical scope of the function which is this case is count being 0 when interval is created.

So, doing setCount(count +1) is pointless when it understands that count is 0 every second and does not have the updated count value. So, what about the ref? Why was it not incremented? Well it was. It is getting incremented everytime the interval function runs. We are not able to see its updated value because, unlike useState, useRef doesn’t trigger a re-render.

Okay, even if the count was 0 according to the function inside the interval, it should have updated the state to 0+1 =1 every time the interval ran, right? And, it should have triggered a re-render, showing the updated value of the ref on the screen, right? Because, technically that’s what our code and the closures suggest!

Technically, yes. But that’s an optimisation provided by React, it somewhat ignores the re-render due to the state update, to put it in that way, when the previous state is exactly same as the requested updated state(a primitive value in this case is easier to understand as 1===1 is always true) and thus preventing further unnecessary re-renders.

Okay so what is the solution to this?

Simple! To make sure that the setState function always has the updated state value, just use setCount((prev)=>prev+1);

After changing the implementation of setCount

Lovely to see all of those in sync, right?

Lets check out another example.

Another simple piece of code. You can guess it now, right?
Yes, it renders ‘1’ after the click of button, even though we have asked the function to update the state 5 times! But like I said, Stale Closures.

Just because we are updating the state multiple times, doesn’t guarantee us that one update will execute after the previous one has been completed or when exactly it is scheduled.
Well, that does end the debate about “useState is synchronous or asynchronous?”, right? xD. The delay has to do with how React batches the state updates. Do read about the reconciliation and diffing algorithm in react. So the bottomline is, since there is no guarantee of one update executing after the previous one has been done and re-rendered, the count value will be 0(initial value) for all the setCount statements. To have access to the latest value, like I suggested earlier, setCount((prev)=>prev+1); in all 5 statements. That would give the expected result.

Hope you liked the article and found it helpful. Do share with those who might find this interesting.

Thank you :)

--

--