Back to roadmaps react Course

useEffect Lifecycle and Cleanup

Functional components should remain pure—returning UI structure from input props and state. Side effects (like API requests, timer settings, and event subscriptions) must be handled inside the useEffect hook.


1. Syntax of useEffect

The useEffect hook takes a callback function containing the side effect logic, and an optional dependency array:

import { useEffect } from "react";

useEffect(() => {
  // Side effect logic goes here
}, [dependency1, dependency2]);

2. Dependency Array Control Modes

The dependency array controls when the side effect callback executes:

No Dependency Array

Runs the callback on the initial mount and after every single render of the component. Use this rarely, as it can cause performance bottlenecks.

useEffect(() => {
  console.log("Render occurred");
});

Empty Dependency Array

Runs the callback exactly once when the component first mounts (initial render). Useful for initial database loads.

useEffect(() => {
  console.log("Component mounted");
}, []);

With Specific Dependencies

Runs on mount, and only when one or more of the specified dependencies change.

useEffect(() => {
  console.log("UserID changed, reloading details...");
}, [userId]);

3. The Cleanup Function

If your side effect registers a timer, sets up a subscription, or listens to global window events, you must return a cleanup function to release resources when the component unmounts or before running the effect again.

Failing to clean up side effects causes memory leaks.

import { useState, useEffect } from "react";

function WindowResizeMonitor() {
  const [width, setWidth] = useState(window.innerWidth);

  useEffect(() => {
    const handleResize = () => setWidth(window.innerWidth);
    
    // Add event listener on mount
    window.addEventListener("resize", handleResize);
    
    // Return cleanup function
    return () => {
      // Remove event listener when component unmounts
      window.removeEventListener("resize", handleResize);
    };
  }, []); // Empty array ensures listener is set up only once

  return <p>Window Width: {width}px</p>;
}

4. Avoiding Infinite Loops

If you update a state variable inside useEffect and that state variable is also listed in the dependency array, you will create an infinite loop that crashes the browser:

// WARNING: Infinite Loop Example
const [count, setCount] = useState(0);

useEffect(() => {
  // Triggers state change -> triggers render -> triggers effect -> loops indefinitely!
  setCount(count + 1);
}, [count]);
Published on Last updated: