React useEffect Hook

The useEffect Hook in React enables the execution of side effects in your components. Common side effects include: data fetching, direct DOM manipulation, and setting up timers.



Using useEffect

useEffect takes two arguments, with the second one being optional:

useEffect(<effect function>, <dependencies>);



We’ll use a timer as an illustrative example.

Example

Using setTimeout() to count 1 second after the initial render:

import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";

function Countdown() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);
  });

  return <h1>The component has rendered {count} times!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Countdown />);

However, this keeps counting continuously.

useEffect executes on every render. This means that when count changes, the component re-renders, triggering the effect again. This isn’t desirable. There are ways to control when side effects should run.

You should always provide the second parameter, which can take an array of dependencies.




Controlling the Execution

Example 1: No dependencies:

useEffect(() => {
  //Executes on every render
});

Example 2: Empty array:

useEffect(() => {
  //Executes only on the initial render
}, []);

Example 3: With dependencies (props or state values):

useEffect(() => {
  //Executes on initial render and whenever any dependency changes
}, [prop, state]);



To fix our timer issue, let’s ensure the effect only runs on the initial render.

Example

Run the effect only once:

import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";

function Countdown() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    setTimeout(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);
  }, []); // <- empty array dependency

  return <h1>The component has rendered {count} times!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Countdown />);


Example

Here is an example where the useEffect Hook depends on a variable. The effect re-runs whenever the count variable updates:

import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";

function Multiplier() {
  const [count, setCount] = useState(0);
  const [result, setResult] = useState(0);

  useEffect(() => {
    setResult(count * 2);
  }, [count]); // <- `count` is the dependency

  return (
    <>
      <p>Count: {count}</p>
      <button onClick={() => setCount(c => c + 1)}>Increase</button>
      <p>Result: {result}</p>
    </>
  );
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Multiplier />);

When there are multiple dependencies, they should all be included in the useEffect dependency array.




Cleaning Up Effects

Certain effects need to be cleaned up to prevent memory leaks. This includes timeouts, subscriptions, event listeners, and other effects that are no longer necessary.

Cleanup is done by returning a function at the end of the useEffect Hook.

Example

Clean up the timer:

import { useState, useEffect } from "react";
import ReactDOM from "react-dom/client";

function Countdown() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    let timer = setTimeout(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);

    return () => clearTimeout(timer);
  }, []);

  return <h1>The component has rendered {count} times!</h1>;
}

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<Countdown />);
Scroll to Top