Skip to main content

Custom hook: useInterval

· 3 min read
Filip Tammergård
Software Engineer at Frilans Finans

setInterval is a JavaScript function that can be used to do things in an interval. I have used setInterval multiple times on this blog, for example in the posts Birthday maths and Letter game.

Using setInterval in React can sometimes feel a bit wonky, since you have to remember to run clearInterval to avoid memory leaks, and pausing the interval isn't trivial.

I decided to make a custom hook that simplifies the usage. Here's the result:

import { useEffect } from "react"

export const useInterval = (callback: () => void, delay: number | null) => {
useEffect(() => {
if (delay !== null) {
const id = setInterval(callback, delay)
return () => clearInterval(id)
}
}, [callback, delay])
}

This is how easy the hook is to use:

useInterval(() => {
console.log("Another second!")
}, 1000)

The syntax is identical to setInterval, which makes it intuitive and easy to use. But it lets you skip worrying about cleanup.

The hook comes even more in handy when you need to be able to set the interval time dynamically and when you need the possibility to pause the interval.

const [isRunning, setIsRunning] = useState(false)
const [delay, setDelay] = useState(500)
const [count, setCount] = useState(0)

useInterval(
() => {
setCount((prev) => prev + 1)
},
isRunning ? delay : null,
)

Here, isRunning is a state variable that's initially set to false, but can be toggled to true to start the interval. And the interval time, delay, can be changed when needed.

Let's say we want to have a counter that increments at a custom pace but is automatically paused when the pace gets too high (in this case 50 ms). Like this:

0

Try changing delay to 50 ms and then to 49 ms and you'll see the counter pauses. It doesn't reset, so changing back to 50 ms will make the counter continue from where it was. Here's how it's done:

import { useInterval } from "hooks/useInterval"
import { useEffect, useState } from "react"

const Counter = () => {
const [isRunning, setIsRunning] = useState(false)
const [delay, setDelay] = useState(500)
const [count, setCount] = useState(0)

useInterval(
() => {
setCount((prev) => prev + 1)
},
isRunning ? delay : null,
)

useEffect(() => {
if (delay < 50) {
setIsRunning(false)
} else {
setIsRunning(true)
}
}, [delay])

const handleDelayChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const input = parseInt(e.target.value, 10)
setDelay(input)
}

return (
<div>
<span>Choose delay [ms]...</span>
<br />
<input type="number" onChange={handleDelayChange} value={delay} />
<p>{count}</p>
</div>
)
}

export default Counter

Very easy to use compared to doing it with the plain setInterval function in every implementation.