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.

Implementing a timer using React - This doesn't work.
It gets stuck on one

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.

Implementing a timer using React - This works.
It works!

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.

Implementing a timer using React - This works but this is an anti-pattern.
This also works but this is an anti-pattern

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.

Implementing a timer using React - This works and this is another way.
this also works as expected

Although both methods work just fine, I find the first method to be more intuitive and Reactive.

1 Comment

Leave a Reply