Using the useEffect() hook in React

OpenJavaScript 0
Reading Time: 2 minutes 🕑

Last updated: September 13, 2022.

The useEffect() hook is useful for executing code only once after the initial rendering of a component. It can also run the code initially and every time a value created by useState() updates.

In the terminology of React, code executed with useEffect() is known as a side effect.

Table of contents

useEffect() syntax

The useEffect() hook accepts two arguments.

The first is a function containing the code to be executed.

The second is an array list of dependencies. These are values which, when updated, will trigger useEffect() to run again.

For example, inside a component, the code below would start a timer that triggers an alert 1 second after the component has rendered.

useEffect(
  // Code to run in nested function:
  () => {
    setTimeout(() => {
      alert("Time's up!");
    }, 1000);
  },
  [] // dependencies
);

Usage warning

If you omit the dependency array, useEffect() will run on every rerender.

This can cause an unintentional infinite loop of useEffect() running if it updates a state value that triggers a rerender.

Usage

Running useEffect() only once

Below is the example of a counter, which useEffect() increases in value by 1 each time it runs:

import { useState, useEffect } from "react";

function App() {
  const [renders, setRenders] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      setRenders(renders + 1)
    }, 1000);
  },[]);

  return <h1>Number of renders: {renders}</h1>;
}

export default App;

This code increases the value of renders by 1 only once. This is because an empty array is given as the second argument, which specifies that there are no dependencies to watch that would trigger useEffect() to run again.

useEffect() with a dependency

Now, in the following example, renders is added to the dependency list:

import { useState, useEffect } from "react";

function App() {
  const [renders, setRenders] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      setRenders(renders + 1)
    }, 1000);
  },[renders]);

  return <h1>Number of renders: {renders}</h1>;
}

export default App;

The effect of doing this is to create an infinite loop of rerendering.

This is because useEffect() updates the state value renders. And updating a state value triggers a rerender. This causes useEffect() to run again as renders is listed as a dependency. This updates the state value again, triggering another rerender and the running of useEffect(). And so on.

Side effect cleanup

Anything that was created by useEffect() and can be cleaned up should be. This prevents conflict if the component is rerendered.

Cleanup is done by creating a return function in useEffect(). It is run every time a component unmounts.

For example, the timer we created can be cleared:

import { useState, useEffect } from "react";

function App() {
  const [renders, setRenders] = useState(0);

  useEffect(() => {
    const timeout = setTimeout(() => {
      setRenders(renders + 1)
    }, 1000);

  return function() {
    clearTimeout(timeout);
  }

  },[]);

  return <h1>Number of renders: {renders}</h1>;
}

export default App;