Implementing a timer using React
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 setInterval
.
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.
Though the 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 (setCounter
).
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.
Solution 1
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 setCounter
method.
Solution 2
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 localCounter
is a JavaScript variable and we increment it without depending on the dispatch method, we make sure the variable gets updated consistently.
Although both methods work just fine, I find the first method to be more intuitive and Reactive.
[…] a user can trigger on mouse click. If you wonder how you can solve this yourself, check out this article. […]