Implementing a timer using React might seem straightforward until you actually try to implement one. This seemingly run-of-the-mill task can put your React knowledge to a stern test.
The obvious way
First, let us try the obvious way–creating a state and incrementing it using
When you run the app, you will realize the timer goes up by one from zero and then stops there.
The reason is simple. The
setInterval method is called inside a
useEffect hook that has an empty array as a dependency. This means that the
useEffect hook fires the callback function only during the initial render. So, the
setInterval method is called only once and rightly so.
However, this also means the callback function is initialized only once. The
counter value during the first initialization is 0. But this does not change within the callback function even when the
setCounter method is used to increment the value.
counter value in the React component will be updated, the copy of the
counter value the callback function has will remain 0. So, every time the callback function is fired, the
counter goes up from 0 to 1. So, we are stuck at 1.
The dispatch method is asynchronous
So, in a hypothetical scenario, if we can somehow update the local
counter value within the callback function, we should be able to get the timer working, shouldn’t we? Well, not exactly.
Another important thing to note is that the React dispatch method (
setCounter) is asynchronous. What this means is that we cannot expect the state (
counter) to have been updated soon after we call the dispatch method (
To elaborate further, when the
setCounter method is called the first time, the
counter value is 0. We increment the
counter value by 1 and pass it into the
setCounter method. When we call the
setCounter after a second, there is no guarantee that the
counter value will have been updated to 1. So, we might end up incrementing 0 by 1 and passing it into the
setCounter method once again. So, eventually, we will find that the timer refuses to budge.
One way of solving this issue is to use a callback function within the
setCounter method. React allows us to pass a callback method–that takes the current state value as the argument–into a dispatch method. This will make sure we increment the previous value every time we call the
However, there is another way by which we can accomplish the same result. That is to increment the
counter state within the callback function before passing it into the
setCounter method. This ensures that we are not dependent on the
setCounter method to update the
counter value locally.
As you can see, this works. However, this is an anti-pattern as we are not supposed to update a state value directly. So, let’s use a local variable to make sure we do not offend React’s principles.
Here, we create a local variable called
localCounter and increment it by 1 before setting it to the state. Since
Although both methods work just fine, I find the first method to be more intuitive and Reactive.