May 31, 2023

Robotic Notes

All technology News

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.


Inner workings of setTimeout

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 10sit 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.



Source link