ReactJS useEffect Hook

In React, components are responsible for rendering UI. However, real applications often need to perform tasks outside rendering, such as fetching data, updating the document title, API calls, setting up timers, or listening to events. These tasks are called side effects.

The useEffect Hook is React’s official way to handle side effects inside functional components.

This article explains what useEffect is, why it is required, how it works, and how to use it correctly, with clear explanations and practical examples.


What is useEffect?

useEffect is a React Hook that allows you to run side-effects logic in functional components.

Side effects include:
  • API calls
  • Timers (setTimeout, setInterval)
  • DOM updates
  • Event listeners
  • Logging
  • Subscriptions

Syntax of useEffect


useEffect(() => {
  // side effect logic
}, [dependencies]);


Why Do We Need useEffect?

React components re-render whenever state or props change.
However, some logic should not run during rendering, such as:

  • Fetching data from a server
  • Updating the browser title
  • Starting or cleaning up timers
Before Hooks, this logic lived in lifecycle methods like:
  • componentDidMount
  • componentDidUpdate
  • componentWillUnmount
useEffect replaces all of these in functional components.

Importing useEffect (Required)

To use useEffect, it must be imported from React.


import { useEffect } from "react";

If your component also uses state:

import { useState, useEffect } from "react";


When does useEffect Run?

This depends entirely on the dependency array.

There are three common patterns :
  • Without Dependency Array
  • With Empty Dependency Array
  • With Dependencies
How often the effect runs?

It is completely controlled by the dependency array.

Lets understand each of these with examples:

1. Without Dependency Array (Runs Every Render):

When you don’t pass a dependency array, the useEffect runs after every render.


import { useEffect } from "react";

function Example() {
  useEffect(() => {
    console.log("Component rendered");
  });

  return (
    <div>Hello</div>
  );
}

export default Example;

Explanation:

In Without Dependency Array case, useEffect runs after every render and even runs after every state or prop change.

Usage:
  • Rare
  • Debugging
  • Logging render cycles

2. With Empty Dependency Array (Runs Once)

An empty dependency array tells React to run the effect only once, after the first render.


import { useEffect } from "react";

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

  return (
    <div>Hello</div>
  );
}

export default Example;

This behaves like componentDidMount in class components.

When to use it:
  • API calls on page load
  • Initial setup logic
  • Event listeners
  • Timers or subscriptions
Real Example: Fetching Data(API call) Using useEffect

import { useState, useEffect } from "react";

function Users() {
  const [users, setUsers] = useState([]);

  useEffect(() => {
    fetch("https://jsonplaceholder.typicode.com/users")
      .then(res => res.json())
      .then(data => setUsers(data));
  }, []);

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>
          {user.name}
        </li>
      ))}
    </ul>
  );
}

export default Users;


3. useEffect With Dependencies (Runs on Change)

The effect runs:

  • On first render
  • Again only when specified dependencies change

import { useState, useEffect } from "react";

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

  useEffect(() => {
    console.log("Count changed:", count);
  }, [count]);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

export default Counter;

Why this is powerful?
  • Prevents unnecessary executions
  • Improves performance
  • Keeps logic predictable and controlled

Cleaning Up Effects (Very Important)

Cleanup in useEffect removes side effects when the component no longer needs them. Most importantly, cleanup prevents memory leaks.

Some effects which require cleanup, such as:
  • Notifications
  • Event listeners
  • Success / error banners
  • Auto-dismiss alerts

import React from 'react';
import { useState, useEffect } from 'react';

function App() {
  const [show, setShow] = useState(true);

  useEffect(() => {
    const timeoutId = setTimeout(() => {
      setShow(false);
    }, 3000);

    // cleanup
    return () => {
      clearTimeout(timeoutId);
    };
  }, []);

  if (!show)
    return <h3>Hide it!</h3>;

  return <h3>Saved successfully!</h3>;
}

export default App;


useEffect vs Lifecycle Methods (Conceptual)

Class Lifecycle useEffect Equivalent Explanation
componentDidMount useEffect(() => { ... }, []) Runs once after the component mounts. Commonly used for API calls, subscriptions, or initial setup logic.
componentDidUpdate useEffect(() => { ... }, [dependencies]) Executes whenever specified state or props change. Useful for reacting to updates such as data refresh or DOM updates.
componentWillUnmount Cleanup function inside useEffect Runs just before the component is removed. Used to clean up timers, event listeners, or subscriptions.

Final Summary

The useEffect Hook allows functional components to perform side effects safely and predictably. It runs after rendering, reacts to state and prop changes, and provides a built-in cleanup mechanism for managing resources.

By controlling when effects run using the dependency array, developers can replace class lifecycle methods with a single, flexible API. Whether it’s fetching data, synchronizing state, or managing timers, useEffect is essential for building real-world React applications.

Mastering useEffect is a key milestone in modern React development and sets the stage for advanced Hooks like useReducer, useContext, and performance optimization techniques.