How to Properly Use setTimeout in React
In order to properly call setTimeout
in React (and ensure it is only called once), you need to wrap your call inside a useEffect
hook:
useEffect(() => {
const timeout = setTimeout(() => {
console.log('Called after 1 sec!')
}, 1000)
return () => clearTimeout(timeout)
}, [])
If you were to call the setTimeout
outside a useEffect
hook anywhere in your component, then this would rerun on every render.
Another important step is to always clear the timeout after the component unmounts, which you can do by returning a function from the useEffect
hook. This is the equivalent of using componentWillUnmount
in a class-based component.
Here we can call the clearTimeout
function and pass our reference to the setTimeout
in order to properly free up memory and remove the timeout.
Using State in setTimeout
Using state with setTimeout
can sometimes be tricky. Imagine you have an input field with a submit button that delays the action by 1 second using a setTimeout
.
import { useState } from 'react'
export default function App() {
const [email, setEmail] = useState('')
const submit = () => {
setTimeout(() => {
console.log(email)
}, 1000)
}
return (
<div>
<input onChange={event => setEmail(event.target.value)} />
<button onClick={submit}>Submit</button>
</div>
)
}
If you fill in the input and click on submit, then the value of the input will be logged to the console after one second, just as expected. But what happens if you update the input field after you clicked the button?
In this case, you will still get the value logged, but not the latest value. You will get the value logged out that was available in the input when you clicked on the button. In order to get around this, we need to use refs. Take a look at how our component changes now:
import { useRef, useState, useEffect } from 'react'
export default function App() {
const emailRef = useRef('')
const [email, setEmail] = useState('')
useEffect(() => {
emailRef.current = email
}, [email])
const submit = () => {
setTimeout(() => {
console.log(emailRef.current)
}, 1000)
}
return (
<div>
<input onChange={event => setEmail(event.target.value)} />
<button onClick={submit}>Submit</button>
</div>
)
}
We introduced a new ref using the useRef
hook, and assign its current
value to email
inside a useEffect
hook. Notice that you also want to add email
as a dependency inside the dependency array.
Now inside the setTimeout
, we log out emailRef.current
instead of email
. This way, you will always have the latest value available to you even inside the setTimeout
.
Creating a useTimeout hook
Lastly, let's take a look at how you can create a useTimeout
hook that you can later reuse inside your other components.
export const useTimeout = (callback, timeout) => {
useEffect(() => {
const timeoutReference = setTimeout(callback, timeout);
return () => clearTimeout(timeoutReference);
}, [])
}
This hook expects a callback function as well as a timeout, just like setTimeout
, but it is wrapped inside a useEffect
hook in order to ensure we only call it once. The hook also ensures that the timeout is cleared after the component unmounts. To use it inside a component, you can call it like so:
import { useState } from 'react'
import { useTimeout } from '@hooks'
export default function App() {
useTimeout(() => {
console.log('Called after 1 sec!')
}, 1000)
return ...
}

Rocket Launch Your Career
Speed up your learning progress with our mentorship program. Join as a mentee to unlock the full potential of Webtips and get a personalized learning experience by experts to master the following frontend technologies: