How to use JavaScript scheduling methods with React hooks
Tags: javascript, beginners, react, codenewbie
Published: 2020-11-04 05:16:06

At times, you may want to execute a function at a certain time later or at a specified interval. This phenomenon is called, `scheduling a function call`. 

JavaScript provides two methods for it,
- setInterval
- setTimeout

Using these scheduling methods with `reactJs` is straightforward. However, we need to be aware of a few small gotchas to use them effectively. In this article, we will explore the usages of `setInterval` and `setTimeout` methods with `reactJS` components. 

Let us build a simple `Real-time Counter` and `Task Scheduler` to demonstrate the usages.

# What is setInterval?
The `setInterval` method allows us to run a function periodically. It starts running the function after an interval of time and then repeats continuously at that interval.

Here we have defined an interval of 1 second(1000 milliseconds) to run a function that prints some logs in the browser console.

```js
const timerId = setInterval(() => {
  console.log('Someone Scheduled me to run every second');
}, 1000);
```
The `setInterval` function call returns a `timerId ` which can be used to cancel the timer by using the `clearInterval` method. It will stop any further calls of setInterval.

```js
clearInterval(timerId).
```

# What is setTimeout?
The `setTimeout` method allows us to run a function `once` after the interval of the time. Here we have defined a function to log something in the browser console after 2 seconds.

```js
const timerId = setTimeout(() => {
  console.log('Will be called after 2 seconds');
}, 2000);
```
Like setInterval, setTimeout method call also returns a `timerId`. This id can be used to stop the timer.

```js
clearTimeout(timerId);
```

# Real-time Counter
Let us build a `real-time counter` app to understand the usage of the `setInterval` method in a react application. The real-time counter has a toggle button to start and stop the counter. The counter value increments by 1 at the end of every second when the user starts the counter. The user will be able to stop the counter or resume the counter from the initial value, zero.

We will be using some of the built-in hooks from react but the same is possible using the React Class component as well.

This is how the component behaves,

> ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/li2m6k1kvn8qay50vgbs.gif)
*Figure 1: Using setInterval and React Hooks*

**Step 1**: Let's get started by importing `React` and two in-built hooks, `useState` and `useEffect`.

```js
import React, { useState, useEffect} from "react";
```
**Step 2**:  We will need two state variables. First to keep track of the start-stop toggle of the `real-time` button and second, for the `counter` itself. Let's initialize them using the `useState` hook.

The hook `useState` returns a pair. First is the current state and second is an updater function. We usually take advantage of array destructuring to assign the values. The initial state value can be passed using the argument.

```js
 const [realTime, setRealTime] = useState(false);
 const [counter, setCounter] = useState(0);
```
**Step 3**: The hook `useEffect` is used for handling any sort of side effects like state value changes, any kind of subscriptions, network requests, etc. It takes two arguments, first a function that will be invoked on the run and, an array of the values that will run the hook.

It runs by default after every render completes. However, we can make it run whenever a particular value changes by passing it as the second parameter. We can also make it run just once by passing an empty array as the second parameter.

In this case, we are interested to run the `useEffect` hook when the user toggles the real-time button(for start and stop). We want to start the interval when the `realTime`  state variable is true and cancel/stop the interval when the state variable value is false. Here is how the code structure may look like,

```js
useEffect(() => {
  let interval;
  if (realTime) {
    interval = setInterval(() => {
      console.log('In setInterval');
      // The logic of changing counter value to come soon.
    }, 1000);
  } else {
     clearInterval(interval);
  }
  return () => clearInterval(interval);
}, [realTime]);
```
We have used the `setInterval` method inside the `useEffect` Hook, which is the equivalent of the `componentDidMount` lifecycle method in Class components. At this point, it just prints a log at the end of a 1-second interval. We are clearing the timer in two cases. First, when the value of the `realTime` state variable is false, and second, the component is unmounted.

**Step 4**: Time to increase the counter. The most straightforward way to do that will be, call the `setCounter` method and set the incremented value of the counter like this,

```js
setCounter(counter => counter + 1);
```
But let us be aware of one important thing here. `setInterval` method is a [closure](https://blog.greenroots.info/understanding-javascript-closure-with-example-ckd17fci5001iw5s1fju4f8r0), so, when setInterval is scheduled it uses the value of the counter at that exact moment in time, which is the initial value of 0. This will make us feel, the state from the `useState` hook is not getting updated inside the `setInterval` method.

Have a look into this code,

```js
useEffect(() => {
  let interval;
  if (realTime) {
    interval = setInterval(() => {
      console.log('In setInterval', counter);
    }, 1000);
    setCounter(100);
  } else {
    clearInterval(interval);
  }
   return () => clearInterval(interval);
}, [realTime]);
```
The `console.log('In setInterval', counter);` line will log `0` even when we have set the counter value to `100`. We need something special here that can keep track of the changed value of the state variable without re-rendering the component. We have another hook for it called, `useRef` for this purpose.

`useRef` is like a "box" or "container"  that can hold a mutable value in its `.current` property. We can mutate the `ref` directly using `counter.current = 100`.  Check out [this awesome article](https://blog.bhanuteja.dev/easily-detect-outside-click-using-useref-hook) by Bhanu Teja Pachipulusu to learn about the `useRef` hook in more detail.

Alright, so we need to first import it along with the other hooks.

```js
import React, { useState, useEffect, useRef } from "react";
```
Then, use the `useRef` hook to mutate the ref and create a sync,

```js
const countRef = useRef(counter);
countRef.current = counter;
```
After this, use the `countRef.current` value instead of the `counter` state value inside the function passed to the `setInterval` method.

```js
useEffect(() => {
  let interval;
  if (realTime) {
    interval = setInterval(() => {
      let currCount = countRef.current;
      setCounter(currCount => currCount + 1);
    }, 1000);
  } else {
      clearInterval(interval);
  }
 return () => clearInterval(interval);
}, [realTime]);
```
Now we are guaranteed to get the updated(current) value of the counter all the time.

**Step 5**: Next step is to create two functions for toggling the start-stop button and resetting the counter.

```js
const manageRealTime = () => {
  setRealTime(!realTime);
}

const reset = () => {
  setCounter(0);
}
```
**Step 6**: The last step is to create the rendering part of it.

```html
<div className={style.btnGrpSpacing}>
  <Button
    className={style.btnSpacing} 
    variant={realTime? 'danger' : 'success'} 
    onClick={() => manageRealTime()}>
      {realTime ? 'Stop Real-Time': 'Start Real-Time'}
  </Button>
  <Button 
    className={style.btnSpacing} 
    variant= 'info'
    onClick={() => reset()}>
      Reset Counter
  </Button>
</div>

<div className={style.radial}>
  <span>{counter}</span>
</div>
```
That's all. We have the real-time component working using `setInterval` and react hooks(`useState`, `useEffect` and `useRef`).

# Task Scheduler
Now we will be creating another react component called, `Task Scheduler` which will schedule a task of incrementing a counter by 1 after every 2 seconds. This scheduler will not do anything until the user clicks on a button to schedule again or reset the counter.

This is how the component behaves,

> ![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/6fqdhmeqesua3odj6s3j.gif)
*Figure 1: Using setTimeout and React Hooks*

Just like the `setInterval` method, we will use the `setTimeout` method inside the `useEffect` hook. We will also clear the timer when the component unmount.

```js
useEffect(() => {
  const timer = setTimeout(() => {
    console.log('setTimeout called!');
  }, 1000);
        
  return () => clearTimeout(timer);
}, []);
```
Like setInterval, setTimeout is also a closure. Therefore, we will face a similar situation that the state variable `counter` may not reflect the current value inside the setTimeout method.

```js
useEffect(() => {
  const timer = setTimeout(() => {
    console.log(counter);
  }, 2000);
  setCounter(100);
return () => clearTimeout(timer);
}, []);
```
In the above case, the counter value will remain `0` even when we have set the value to `100`.

We can solve this problem similar to how we have seen it in the previous example. Use the hook `useRef`.

```js
useEffect(() => {
  const timerId = schedule();
  return () => clearTimeout(timerId);
}, []);

const schedule = () => {
  setScheduleMessage('Scheduled in 2s...');
    const timerId = setTimeout(() => {
      let currCount = countRef.current;
      setCounter(currCount => currCount + 1);
      console.log(counter);
  }, 2000);

   return timerId;
}
```
Here we are passing the function `schedule` to the setTimeout method. The `schedule` function makes use of the current value from the reference(ref) and sets the counter value accordingly.

# Demo and Code
You can play around with both the components from here: [Demo: JavaScript scheduling with React Hooks](https://demo.greenroots.info/react/react-js-scheduling-method/)

All the source code used in this article is part of the DemoLab GitRepo([direct source file link](https://github.com/atapas/demolab/blob/master/code/src/demos/react/react-hook-js-schedule.js)). Please feel free to clone/fork/use.

{% github atapas/demolab %}

# In Summary
To summarize,
- `setInterval` and `setTimeout` are the methods available in JavaScript to schedule function calls. Read more about it [from here](https://javascript.info/settimeout-setinterval).
- There are `clearInterval` and `clearTimeout` methods to cancel the timers of the scheduler methods.
- We can use these scheduler methods as similar to any other JavaScript functions in a react component.
- setInterval and setTimeout methods are a closure. Hence when scheduled, it uses the value of the state variable at the time it was scheduled. When the component re-renders a new closure is created but that doesn't change the value that was initially closed over. To fix this situation, we use the `useRef` hook to get the current value of the state variable. You can read further about this solution [from this GitHub issue](https://github.com/facebook/react/issues/14010).

Hope you found this article helpful. You may also like,
- [Understanding JavaScript Closure with example](https://blog.greenroots.info/understanding-javascript-closure-with-example-ckd17fci5001iw5s1fju4f8r0)
- [A Notification Timeline using React](https://blog.greenroots.info/a-notification-timeline-using-react-ckdb6o0yp00wy59s16les9bjn)
- [Understanding Dynamic imports, Lazy and Suspense using React Hooks](https://blog.greenroots.info/understanding-dynamic-imports-lazy-and-suspense-using-react-hooks-ckdfssktb01czpts12krebs1h)
- [Adding a Table row dynamically using React Hook](https://blog.greenroots.info/adding-a-table-row-dynamically-using-react-hook-ckanlxs11024p4us1zs8qljkp)
- [Being Reactive - Usage of Virtual DOM and DOM Diffing](https://blog.greenroots.info/being-reactive-usage-of-virtual-dom-and-dom-diffing-cjvug0m4x006an3s1ne6k8qia)
- [Step by Step Guide: Blend Redux with ReactJs](https://blog.greenroots.info/step-by-step-guide-blend-redux-with-reactjs-ck1tixqp00055avs100ncho2x)

# Before you go…
Thank you for reading this far! You can @ me on Twitter ([@tapasadhikary](https://twitter.com/home)) with comments, or feel free to follow.

I write about my learnings and side projects mostly on JavaScript, Web Development, and JAMstack. Feel free to check out my [blog site](https://blog.greenroots.info/).

If it was useful to you, please Like/Share so that, it reaches others as well.