The setTimeout behavior in the useEffect hook
5 min read
In day-to-day work, you may have come up with a requirement to perform some action after a definite delay time
. I get these demands regularly in one form or another in my daily life. Sometimes you may have a requirement to build a timer in your UI application. These tasks are common in our developer lives.
If you are working on the front end of a web application, the browser has an API to do such work – the setTimeout API. There is another API – setInterval which has regular intervals, but in this article we will talk about setTimeout API.
SetTimeout API
The syntax of setTimeout is
setTimeout(functionRef, delay, param1, param2, param3, ...., paramN);
Here functionRef
is the reference or callback function, the delay
is the time delay and param1 … paramN
are the arguments of the function functionRef
. FunctionRef is a callback function that contains the code that is executed after the delay time expires.
How it works internally:
SetTimeout API is asynchronous in nature. In the image below you can see different parts that are involved in working with the setTimeout API.
If there is a javascript program that includes setTimeout in its code, and when setTimeout is called in the call stack, it sends that request to the setTimeout API and the next line of code (after setTimeout) will be executed.
With the setTimeout API, it will take responsibility for its associated code and wait for the given delay; after the delay time is up, it will press the callback function or functionRef
bound setTimeout code to the callback queue. Since the event loop runs continuously, it will push the setTimeout callback function to the call stack when the call stack becomes empty (an earlier code execution is passed) and then the callback function is called setTimeout callback.
This is how setTimeout works in javascript.
Working with setTimeout in React
We always come with the requirement that we need to do some task after some delay period and to do this kind of task in React we usually use setTimeout in React. The inner workings of setTimeout are the same in React as they are in Javascript.
Below is an example of how we use setTimeout in React.
import React, useEffect from "react"
function App()
useEffect(() =>
const timer = setTimeout(() =>
console.log("hello world")
, 10000)
, [])
return <h2>Example 1</h2>
export default App
As you can see in the code above, this is a basic setTimeout example in React.
We have used useEffect
hook. The code inside the hook will only execute after rendering the DOM. After the first rendering of the user interface is done, then the program system enters useEffect
code and execute it.
From useEffect
code block contains setTimeout, the request goes to the setTimeout API and then 10s
it pushes the callback function to the callback queue and at the appropriate time (after the call stack is empty and the event loop will push the code to the call stack), console log
the value will be logged hello world
in the console.
That’s how it goes.
Well, here is a problem with the above code.
The above code has a memory leak problem which means that if somehow App
the component is unmounted (due to a page change or something similar) before the timer expires, the code in the setTimeout block will continue to run even after the component is unmounted. This is a problem. You definitely don’t want to start a component when that component is already unmounted.
To resolve this memory leak issue, we need to clear out
the timer as soon as the component is dismantled. We can do this by returning a function that clears the timer useEffect
code block.
Here is the improved version of the code:
import React, useEffect from "react"
function App()
useEffect(() =>
const timer = setTimeout(() =>
console.log("hello world")
, 10000)
return () =>
clearTimeout(timer)
, [])
return <h2>Example 1</h2>
export default App
In the above code, as you can see, we are clearing the timer inside the returned function. This improved version of the code fixes the memory leak issue.
SetTimeout operation with React state
When the response state is included in the setTimeout callback function, then the situation is already different.
Here is the code to handle the response state with setTimeout:
import React, useEffect, useState from "react"
function App()
const [isDone, setDone] = useState(false)
useEffect(() =>
console.log(isDone)
setTimeout(() =>
console.log(isDone)
, 10000)
, [isDone])
return (
<>
<h2>Example 1</h2>
<button onClick=() => setDone(true)>Done</button>
</>
)
export default App
In the above code I added a button that does isDone
variable true
when the user clicks on it. Since the time delay for setTimeout is 10 seconds, in between if we change the state (isDone
) clicking the button will change the state but console.log
within setTimeout
the code will still print the old value false
.
Fix response state issue with setTimeout:
We can solve the above problem using clearTimeout
as we did with the memory leak problem. Below is the improved version of the code that fixes the response state issue with setTimeout.
import React, useEffect, useState from "react"
function App()
const [isDone, setDone] = useState(false)
useEffect(() =>
console.log(isDone)
const timer = setTimeout(() =>
console.log(isDone)
, 10000)
return () =>
clearTimeout(timer)
, [isDone])
return (
<>
<h2>Example 1</h2>
<button onClick=() => setDone(true)>Done</button>
</>
)
export default App
One problem with this code comes up every time isDone
changed, setTimeout resets and starts again. This can be a problem if you are making an API call inside setTimeout. This reset will trigger another API call which may not be necessary.
If we want to avoid restarting setTimeout every time the time state changes, we can use useRef
hook instead of react state.
import React, useEffect, useRef from "react"
function App()
const isDone = useRef(false)
useEffect(() =>
console.log(isDone)
const timer = setTimeout(() =>
console.log(isDone)
, 10000)
return () =>
clearTimeout(timer)
, [])
const handleDone = status =>
isDone.current = status
return (
<>
<h2>Example 1</h2>
<button onClick=() => handleDone(true)>Done</button>
</>
)
export default App
The useRef
will solve the problem of not accessing the latest data in the setTimeout code we saw in useState
hook.
I hope you enjoyed the article.
Thanks for reading. Keep coding and keep solving problems.