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:






