React useEffect Hook: Beginner’s Guide with Examples

In React, the useEffect Hook allows you to perform side effects in functional components.

Side effects are operations that interact with the outside world or are not purely about rendering:

  • Fetching data from APIs

  • Subscribing to events

  • Updating the document title

  • Setting timers or intervals

Before Hooks, these were only possible in class components using componentDidMount, componentDidUpdate, and componentWillUnmount.

useEffect combines all of these lifecycle methods into a single Hook.

Syntax

useEffect(() => {
  // Code to run (side effect)
  return () => {
    // Cleanup code (optional)
  };
}, [dependencies]);
  • First argument: function containing your side effect

  • Return function: optional cleanup (runs on unmount or before next effect)

  • Dependencies array: controls when the effect runs

Example 1: useEffect on Component Mount

import { useEffect } from "react";

function App() {
  useEffect(() => {
    console.log("Component mounted!");
  }, []); // Empty dependency array → runs once on mount

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

[] means the effect runs only once, similar to componentDidMount in class components.

Example 2: useEffect on State Change

import { useState, useEffect } from "react";

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

  useEffect(() => {
    console.log(`Count updated: ${count}`);
  }, [count]); // Runs whenever 'count' changes

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

The effect runs every time the dependency (count) changes.

Example 3: useEffect Without Dependencies

 
useEffect(() => {
console.log("Effect runs on every render");
});
  • Without a dependency array, the effect runs after every render.

  • Useful for debugging or continuous side effects, but can affect performance.

Example 4: Cleanup with useEffect

import { useEffect, useState } from "react";

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

  useEffect(() => {
    const interval = setInterval(() => {
      setCount(prevCount => prevCount + 1);
    }, 1000);

    // Cleanup function
    return () => clearInterval(interval);
  }, []); // Empty array → setup once

  return <h1>Timer: {count}</h1>;
}
  • The cleanup function runs when the component is unmounted.

  • Prevents memory leaks (important for timers, subscriptions, or event listeners).

Example 5: Updating Document Title

import { useState, useEffect } from "react";

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

  useEffect(() => {
    document.title = `Clicked ${count} times`;
  }, [count]); // Runs whenever count changes

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

Shows a real-world use case of useEffect.

Best Practices for useEffect

PracticeExplanation
Use dependencies wiselyList all variables the effect depends on to avoid bugs.
Cleanup side effectsPrevent memory leaks with return () => { ... }.
Avoid heavy computations inside effectKeep it fast; move heavy logic outside if possible.
Multiple effectsSplit into multiple useEffect calls instead of combining unrelated effects.

FAQ

Q1: What is a side effect?
Any operation outside rendering, e.g., API calls, timers, DOM manipulation.

Q2: Can I have multiple useEffect Hooks?
Yes! Each can handle a separate effect for better code organization.

Q3: What happens if I don’t pass a dependency array?
The effect runs after every render, which may hurt performance.