You may want to use setInterval function in a React application and may want to initialize this inside an useEffect hook during the initial run of the application. Your code may look like this with normal setup
import React, { useState, useEffect } from 'react'
function Ticker() {
let [count, setCount] = useState(0);
useEffect(() => {
let interval = setInterval(() => {
setCount(count + 1)
}, 1000)
return () => {
clearInterval(interval);
}
}, [])
return (
<div>
{count}
</div>
)
}
export default Ticker
If you run the application with the above code, then you will find that the count value do not go past 1. It will get increment from 0 to 1 and stops there. Now, it appears like the setInterval is not working in React.
Before proceeding to check the working solutions for the above problem, understanding why its not working with the previous code can give better idea on how React and JavaScript itself works.
Why setInterval is not working in React:
When we run the application with the above code, React for the first time sets the value of count to zero, passes the same value to setInterval callback function and renders the application. So we see the value 0 first.
After 1 second, the callback function inside the setInterval gets executed. Now the interesting things start happening in the application. The context in which the setInterval callback function gets executed will be different, so the count variable here is not same as the count inside the component. The count variable inside the setInterval callback function will be taken from the function’s closure whose value will be 0. So every time the setInterval callback function runs, it takes the count value from its closure which is 0 and passes 0+1 to setCount. So the setCount function will be called every time with the value 0+1 that is 1.
To inspect this, we can have a console log statement inside the setInterval callback function like this
console.log('Count Value inside setInterval: ', count)
Now you can see ‘Count Value inside setInterval: 0’ in the browser console for every function call.
There are different ways to solve this problem in React.
Solution 1:
By using previous value: Instead of passing absolute value to setCount, we can pass relative value by using the previous value.
example:
let interval = setInterval(() => {
setCount(previousValue => previousValue + 1)
}, 1000)
Solution 2:
By adding count dependency to useEffect hook.
example:
useEffect(() => {
let interval = setInterval(() => {
setCount(count + 1)
}, 1000)
return () => {
clearInterval(interval);
}
}, [count])
In this example, when the value of count changes, React will re-run the useEffect ( which did not happen with first code example, since we passed empty dependency array which makes react run that effect only during the first run), so we are clearing and re-setting setInterval every time.
Solution 3:
By Changing the value inside the setInterval callback function closure.
example:
useEffect(() => {
let interval = setInterval(() => {
setCount(count++)
}, 1000)
return () => {
clearInterval(interval);
}
}, [])
In this example, we are changing the value of count which is there in the setInterval callback function closure by doing count++ which is equivalent to count = count + 1.
Though we are changing the value of count which is inside the callback function, it may look like we are directly changing the value of a state variable, which may lead to confusion. So use this example with caution.
Leave A Comment