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.
consttimerId=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.
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.
consttimerId=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.
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,
Figure 1: Using setInterval and React Hooks
Step 1: Let's get started by importing React and two in-built hooks, useState and useEffect.
importReact,{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.
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,
useEffect(()=>{letinterval;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,
setCounter(counter=>counter+1);
But let us be aware of one important thing here. setInterval method is a closure, 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.
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 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.
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,
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.
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.
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.
useEffect(()=>{consttimerId=schedule();return()=>clearTimeout(timerId);},[]);constschedule=()=>{setScheduleMessage('Scheduled in 2s...');consttimerId=setTimeout(()=>{letcurrCount=countRef.current;setCounter(currCount=>currCount+1);console.log(counter);},2000);returntimerId;}
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.
Demolab is my fantasy project created to understand the power of JAMstack using JavaScript(Reactjs), API(Netlify and Aws) and pre-built Markup(Gatsby).
demolab
Demolab is my fantasy project created to understand the power of JAMstack using JavaScript(Reactjs), API(Netlify and Aws) and pre-built Markup(Gatsby).
setInterval and setTimeout are the methods available in JavaScript to schedule function calls. Read more about it from here.
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.
Hope you found this article helpful. You may also like,