JavaScript Timer with React Hooks
Tags: react, chroniker, hooks, timer
Published: 2019-09-12 21:55:36

---
title: JavaScript Timer with React Hooks
published: true
description: a custom hook that counts time
tags: React, chroniker, hooks, timer
series: chroniker
---

This post is a part of a series about the ins and outs of [chroniker.co](https://chroniker.co/)

The main focus of chroniker.co is time tracking. Thus a way to accurately track time was necessary. This might seem like a trivial problem that can  be solved with **setTimeout** or **setInterval**. However, things are not that simple with these functions as you will soon find out.

The code I used to track time on this website has changed significantly over time. I started with a simple react hook that used a **setInterval** and it worked. It was't fancy, and it was accurate when compared to a stopwatch. But when you leave it on for long periods of time, strange things begin to happen. 

It becomes VERY inaccurate. If you set **setInterval** to fire every second, it will absolutely not do that precisely. Sometimes it will wait, sometimes it will be on point. And the result you get is an error that grows with each iteration. There is a great [article](https://javascript.info/settimeout-setinterval) that goes into detail about this issue. 

Another thing to note is what happens to a **setInterval** when the browser tab in which it is running is inactive/not focused. The browser will re-route its resources to the focused tab, leaving **setInterval** running with great delays. It will also default to once per second even if you set it to run more frequently. Similar things happen when the computer goes into sleep mode. This is very inconvenient if your website is built around time tracking. 

Bottom line is: NEVER trust that **setInterval** will run your code at exactly the interval you specified.


The solution to this problem comes in many shapes and sizes. However they all have one thing in common: Use the absolute value of Date(). The idea is to save the time before the interval starts and when it executes. That way you can subtract one from another and get the actual interval. You can then adjust either the interval or the logic that consumes it to get some accurate readings. 

For example:

```javascript
const doSomething = () => {
//your code
}

setInterval(() => {
  doSomething();
}, 1000);
```
The above code tries to run **doSomething** every second, so it's easy to predict what the absolute time after it finishes should be:

```javascript
new Date().getTime() + 1000;
```
However in reality the absolute time when this interval ends will always vary because of the aforementioned reasons and you will have a new time that is either larger or smaller than your prediction.
By subtracting one from another you will get the variation that you need to factor into your timekeeping calculations. 

Even with this method your 'clock' will not be accurate to the millisecond like a typical digital stopwatch, however it will be very close. It will start to drift only when counting large intervals of time in the order of days. This type of accuracy was sufficient for my purpose.

When I come across pieces of logic like this I always try to package them into a react hook. Here is what I came up with:
```javascript

import { useEffect, useRef, useState } from 'react';

const usePreciseTimer = (handler, periodInMilliseconds, activityFlag) => {
  const [timeDelay, setTimeDelay] = useState(1);
  const savedCallback = useRef();
  const initialTime = useRef();

  useEffect(() => {
    savedCallback.current = handler;
  }, [handler]);

  useEffect(() => {
    if (activityFlag) {
      initialTime.current = new Date().getTime();
      const id = setInterval(() => {
        const currentTime = new Date().getTime();
        const delay = currentTime - initialTime.current;
        initialTime.current = currentTime;
        setTimeDelay(delay / 1000);
        savedCallback.current(timeDelay);
      }, periodInMilliseconds);

      return () => {
        clearInterval(id);
      };
    }
  }, [periodInMilliseconds, activityFlag, timeDelay]);
};

export default usePreciseTimer;



```
First let me explain the hook definition: 

```javascript
usePreciseTimer = (handler, periodInMilliseconds, activityFlag)

```
This hook expects us to pass it a **handler**, something it can run every interval - **periodInMilliseconds**, and it should only run it if the **activityFlag** evaluates to true. Because I need to display the total elapsed time, I also pass the expired time to the handler so that it can add it to the current elapsed time.

```javascript
 useEffect(() => {
    savedCallback.current = handler;
  }, [handler]);

```
Here I make use of the builtin **useRef** hook to make sure I update the local handler if it ever changes.

```javascript
if (activityFlag) {
      initialTime.current = new Date().getTime();
      const id = setInterval(() => {
        const currentTime = new Date().getTime();
        const delay = currentTime - initialTime.current;
        initialTime.current = currentTime;
        setTimeDelay(delay / 1000);
        savedCallback.current(timeDelay);
      }, periodInMilliseconds);

      return () => {
        clearInterval(id);
      };
    }
```

This is where the main logic is executed. If the **activityFlag** is true, we first save the current time as a reference. Once **periodInMilliseconds** elapses, our **setInterval** should start executing. At this point we take another measurement.

We subtract our start time form the real absolute time ending up with a **delay**. The resulting delay is the actual elapsed time, it can be larger or smaller than **periodInMilliseconds**. 

We then convert the delay time into seconds and pass it to our handler in order to tell it how much time has passed, but not before updating the **initialTime** with the new **currentTime** for the next cycle.

```javascript
return () => {
        clearInterval(id);
      };
```
Here we cleanup the setInterval when the hook gets unmounted.



Lastly here is the actual way this hook is used:

```javascript
 usePreciseTimer(updateTime, 1000, state.isActive);

```

You can check the accuracy live on [chroniker.co](https://chroniker.co/)

I am curious on how to further improve this, let me know what you think :)