Mastering useEffect in React – Explained with Real Examples (2025 Guide)

12/07/2025

Mastering useEffect in React – Explained with Real Examples (2025 Guide)

Learn how to use the useEffect hook in React like a pro. This 2025 guide covers syntax, dependencies, cleanup functions, and common use cases with real-world examples to help you master side effects in functional components.

Mastering useEffect in React (with Real Examples)

Introduction to useEffect

The useEffect hook is a fundamental part of building React applications with functional components. It allows you to perform side effects in your components. Side effects are operations that interact with the outside world, such as data fetching, subscriptions, manually changing the DOM, or setting up event listeners.

Before hooks, you'd typically handle these in lifecycle methods like componentDidMount, componentDidUpdate, and componentWillUnmount in class components. useEffect consolidates this logic into a single, more intuitive API.

Basic Syntax

The useEffect hook accepts two arguments:

  1. A function that contains the side-effect logic.
  2. An optional dependency array.

import React, { useEffect } from 'react';

function MyComponent() {
  useEffect(() => {
    // This function runs after every render of the component
    // (unless dependencies are specified).
    console.log('Component rendered or updated!');
  }); // No dependency array means it runs after every render

  return <div>Hello, useEffect!</div>;
}
                

Understanding the Dependency Array

The dependency array is crucial for controlling when your effect runs. It tells React to re-run the effect only when the values in this array change.

1. No Dependency Array (Runs on Every Render)

If you omit the second argument, the effect will run after every render of the component. This is rarely what you want, as it can lead to performance issues and infinite loops.


useEffect(() => {
  console.log('This runs after EVERY render!');
});
                

2. Empty Dependency Array (Runs Once on Mount)

Passing an empty array [] as the second argument tells React to run the effect only once after the initial render (component mount) and never again on re-renders. This is similar to componentDidMount.


useEffect(() => {
  console.log('This runs only once on component mount!');
  // Ideal for initial data fetching, setting up subscriptions
}, []); // Empty array
                

3. With Dependencies (Runs on Mount and When Dependencies Change)

If you include variables in the dependency array, the effect will re-run whenever any of those variables change between renders. This is the most common use case.


useEffect(() => {
  console.log(`The count is now: ${count}`);
}, [count]); // Effect re-runs when 'count' changes
                

The Cleanup Function

Sometimes, your side effect needs a "cleanup" phase. For example, if you set up a subscription or an event listener, you need to unsubscribe or remove the listener when the component unmounts or before the effect re-runs. You can do this by returning a function from your useEffect callback.


useEffect(() => {
  console.log('Effect is running...');

  // This function is the cleanup function
  return () => {
    console.log('Cleanup function is running!');
    // Perform cleanup here (e.g., unsubscribe, clear timers)
  };
}, []); // Runs once on mount, cleanup runs on unmount
                

The cleanup function runs:

  • Before the effect re-runs (if dependencies change).
  • When the component unmounts.

Real-World Examples

Example 1: Fetching Data on Component Mount

A classic use case is fetching data when the component first renders.


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

function UserProfile({ userId }) {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    const fetchUser = async () => {
      setLoading(true);
      setError(null);
      try {
        const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();
        setUser(data);
      } catch (e) {
        setError(e.message);
      } finally {
        setLoading(false);
      }
    };

    fetchUser();
  }, [userId]); // Re-fetch when userId changes

  if (loading) return <p style="margin-bottom: 15px; line-height: 1.7;">Loading user data...</p>;
  if (error) return <p style="color: red; margin-bottom: 15px; line-height: 1.7;">Error: {error}</p>;
  if (!user) return <p style="margin-bottom: 15px; line-height: 1.7;">No user found.</p>;

  return (
    <div>
      <h3 style="color: #2980b9; font-size: 1.5em; margin-top: 0; border-bottom: 1px solid #2980b9; padding-bottom: 5px; margin-bottom: 15px;">User Profile</h3>
      <p style="margin-bottom: 15px; line-height: 1.7;"><strong style="color: #e67e22;">Name:</strong> {user.name}</p>
      <p style="margin-bottom: 15px; line-height: 1.7;"><strong style="color: #e67e22;">Email:</strong> {user.email}</p>
      <p style="margin-bottom: 15px; line-height: 1.7;"><strong style="color: #e67e22;">Phone:</strong> {user.phone}</p>
    </div>
  );
}
                

Example 2: Updating Document Title

You can use useEffect to interact with the browser's DOM, like changing the document title based on component state.


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

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

  useEffect(() => {
    document.title = `Count: ${count}`;
    console.log('Document title updated!');
  }, [count]); // Re-run when count changes

  return (
    <div>
      <p style="margin-bottom: 15px; line-height: 1.7;">You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}
                

Example 3: Setting Up and Cleaning Up Event Listeners

This demonstrates using the cleanup function to prevent memory leaks by removing event listeners when the component unmounts.


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

function MouseTracker() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  useEffect(() => {
    const handleMouseMove = (e) => {
      setPosition({ x: e.clientX, y: e.clientY });
    };

    window.addEventListener('mousemove', handleMouseMove);
    console.log('Event listener added!');

    // Cleanup function
    return () => {
      window.removeEventListener('mousemove', handleMouseMove);
      console.log('Event listener removed!');
    };
  }, []); // Empty dependency array: runs once on mount, cleans up on unmount

  return (
    <div>
      <p style="margin-bottom: 15px; line-height: 1.7;">Mouse X: {position.x}, Mouse Y: {position.y}</p>
    </div>
  );
}
                

useEffect is a powerful hook that allows you to manage side effects in your React functional components effectively. By understanding its two arguments—the effect function and the dependency array—along with the optional cleanup function, you can write cleaner, more predictable, and more performant React code. Always remember to consider your dependencies carefully to avoid unnecessary re-runs or stale closures.

React's useEffect hook is one of the most powerful — and often misunderstood — tools in modern frontend development. Whether you're fetching data, syncing with local storage, or setting up subscriptions, useEffect allows you to perform side effects in function components with precision.

In this in-depth guide, you’ll master the useEffect hook through real-world examples and clear explanations. We’ll break down the core concepts: how and when it runs, what the dependency array controls, how to handle cleanup functions, and how to avoid common bugs like infinite loops or stale closures.

Perfect for both beginners and experienced developers, this 2025-ready tutorial helps you confidently integrate useEffect into your React apps for cleaner, more predictable behavior — whether you're working on APIs, auth flows, or dynamic UIs.