• React
  • React Hooks

Create a setTimeout hook in React

June 11, 2020 • 2 min read

Easily add timers to your components

React hooks are useful as they allow you to encapsulate and reuse stateful logic in your components. setTimeout is a useful JavaScript method that creates a timer and executes a function or piece of code when the timer expires.


In this example, we will create a hook called useSetTimeout that encapsulates the code for setTimeout, while providing some extra functionalities mentioned below.


Hook Requirements

  • Returns a boolean describing the state of the timer
  • Returns a function for resetting the timer
  • Functionality for controlling when the timer starts

Use Cases

  • Displaying or updating the UI after a certain period of time.

    • Ex: If an API call is taking too long, this hook can be used to display feedback to user after a period of time.
  • Provide user feedback in stages by using multiple useSetTimeout calls.

A basic useSetTimeout

We will start by defining a basic version of this hook that utilizes the useSetTimeout method. This hook will have two parameters.

  1. delay - the number of milliseconds to wait before running the callback
  2. callback - A function that is ran once the timer expires.
1 import { useEffect } from 'react'
2
3 // By default, callback will be an empty function in case the
4 // user does not need to run a specific function.
5 export function useSetTimeout(delay, callback = () => {}) {
6 useEffect(() => {
7 // returns a positive interger ID which identifies the timer
8 const timerId = setTimeout(callback, delay)
9 // clear the timer
10 return () => clearTimeout(timerId)
11 }, [delay, callback])
12 }

A few things are being done here.

  1. We call setTimeout with delay and callback.
  2. setTimeout returns an Id used to identify the timer.
  3. We return a function from the useEffect hook which clears the timer on re-renders preventing memory leaks.

callback and delay are provided to the useEffect's dependency array so that the effect is ran anytime those values change.

useSetTimeout With State

We will call the useState hook to create state for the timer.


Our useState call will return a value we call isRunning that is true by default, and a function to update isRunning, called setIsRunning. Instead of passing callback directly to setTimeout, we now create a new function that will run the callback and call setIsRunning with a value of false, updating isRunning.


Finally, we return isRunning so the caller can use it to determine if the timer has expired.

1 import { useEffect, useState } from 'react'
2
3 // By default, callback will be an empty function in case the
4 // user does not need to run a specific function.
5 export function useSetTimeout(delay, callback = () => {}) {
6 const [isRunning, setIsRunning] = useState(true)
7
8 useEffect(() => {
9 // returns a positive interger ID which identifies the timer
10 const timerId = setTimeout(() => {
11 callback()
12 setIsRunning(false)
13 }, delay)
14 // clear the timer
15 return () => clearTimeout(timerId)
16 }, [delay, callback])
17
18 return isRunning
19 }

How about if we want the caller to be able to reset the timer? In this case, we return the setter function, setIsRunning, so the caller can have access to it. Finally, we need to check if the timer is in a state of running, and if it is, we call setTimeout.


Here is what the code will look like:

1 import { useEffect, useState } from 'react'
2
3 // By default, callback will be an empty function in case the
4 // user does not need to run a specific function.
5 export function useSetTimeout(delay, callback = () => {}) {
6 const [isRunning, setIsRunning] = useState(true)
7
8 useEffect(() => {
9 let timerId
10
11 if (isRunning) {
12 // returns a positive interger ID which identifies the timer
13 timerId = setTimeout(() => {
14 callback()
15 setIsRunning(false)
16 }, delay)
17 }
18
19 // clear the timer
20 return () => clearTimeout(timerId)
21 }, [delay, callback, isRunning])
22
23 return [isRunning, setIsRunning]
24 }

We also add isRunning to the useEffect dependency array so the effect re-runs if the value of isRunning changes.

Here is an example of using this hook:

1 ...
2
3 function App() {
4 const [isRunning, setIsRunning] = useSetTimeout(2000)
5
6 return (
7 <div>
8 {isRunning ? "Loading..." : "Done"}
9 <button onClick={() => setIsRunning(true)}>Click Me</button>
10 </div>
11 )
12 }
13
14 ...

After 2 seconds, isRunning will be set to false, but whenever the user clicks on the button, the timer will reset and wait another 2 seconds before setting it back to false.

Excersises

  1. The last requirement of this hook is to control when the timer starts. Right now, the timer starts as soon as the hook is called. Update the hook so that a timer can be started in a controlled fashion.

  2. How can you use this hook to display different messages to a user after 3, 5 and 8 seconds of them waiting for something to load?

You can find the solutions and full implementation of this hook at this CodeSandbox Link


Newsletter

If you enoyed this tutorial, please consider subscribing to my newsletter for more like it.