← Back to Chapters

useEffect() Hook

⚛️ useEffect() Hook

? Quick Overview

The useEffect() Hook lets you run side effects in React functional components:

  • Fetching data from APIs
  • Updating the DOM (e.g., document.title)
  • Setting up timers and intervals
  • Subscribing / unsubscribing to events

It replaces common class component lifecycle methods:

  • componentDidMount → Run once on mount
  • componentDidUpdate → Run when state/props change
  • componentWillUnmount → Run cleanup before unmount

? Key Concepts

  • Effect function: The main function where you write your side-effect logic.
  • Cleanup function: Optional function returned from the effect, used to clean up timers, subscriptions, etc.
  • Dependency array: Controls when the effect runs.
  • Lifecycle simulation: By configuring dependencies, useEffect can behave like mount, update, or unmount lifecycle methods.

? Lifecycle Simulation Cheat Sheet

Lifecycle Phase useEffect Configuration
componentDidMount useEffect(() => {...}, [])
componentDidUpdate useEffect(() => {...}, [state])
componentWillUnmount return () => {...} inside useEffect

? Syntax

Basic useEffect() syntax with optional cleanup and dependencies:

? View Code Example
// Basic useEffect syntax with optional cleanup and dependencies
useEffect(() => {
doSideEffect();
return () => {
cleanUpSideEffect();
};
}, [dependencies]);
  • The first argument is the effect function where your side-effect logic lives.
  • The second argument is the dependency array, which decides when the effect should re-run.
  • If you return a function from inside the effect, React runs it as cleanup before the effect runs again or when the component unmounts.

? Example 1: Run on Every Render

If you omit the dependency array, the effect runs after every render: initial mount and every state/prop update.

? View Code Example
// useEffect without dependencies runs after every render
import React, { useState, useEffect } from "react";

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

useEffect(() => {
console.log("Rendered or re-rendered");
});

return (
<div className="text-center">
<h4>Count: {count}</h4>
<button className="btn btn-primary" onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}

? What Happens?

  • On first render, the message "Rendered or re-rendered" appears in the console.
  • Every time you click Increment, count changes, the component re-renders, and the effect runs again.
  • Useful for logging, animations, or actions that must happen after every render.

? Example 2: Run Once (on Mount)

Passing an empty dependency array makes the effect run only once after the component mounts (similar to componentDidMount).

? View Code Example
// Effect with empty dependency array runs only once on mount
import React, { useEffect } from "react";

function MountExample() {
useEffect(() => {
console.log("Component mounted");
document.title = "Welcome to React Hooks!";
}, []);

return <h5 className="text-center">Hello React!</h5>;
}

? What Happens?

  • The effect runs only once when the component first appears on the screen.
  • The browser tab title changes to "Welcome to React Hooks!".
  • Great for one-time setup logic such as initial data fetch, analytics, or subscriptions.

?️ Example 3: Run on Specific Updates

Add values to the dependency array to run the effect only when those values change.

? View Code Example
// Effect runs only when the specified dependency changes
import React, { useState, useEffect } from "react";

function UpdateExample() {
const [count, setCount] = useState(0);
const [name, setName] = useState("");

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

return (
<div className="text-center">
<input
placeholder="Enter name"
className="form-control mb-2"
onChange={(e) => setName(e.target.value)}
/>
<p>Name: {name}</p>
<p>Count: {count}</p>
<button className="btn btn-primary" onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}

? What Happens?

  • Typing into the input updates name but does not trigger the effect.
  • Clicking Increment changes count and triggers the effect, logging the new value.
  • This mimics componentDidUpdate for a specific piece of state.

⏱️ Example 4: Cleanup on Unmount (Timer)

Returning a function from inside useEffect lets you clean up timers, listeners, or subscriptions when the component unmounts (similar to componentWillUnmount).

? View Code Example
// Timer with cleanup to clear the interval when component unmounts
import React, { useState, useEffect } from "react";

function Timer() {
const [seconds, setSeconds] = useState(0);

useEffect(() => {
const interval = setInterval(() => {
setSeconds((s) => s + 1);
}, 1000);

return () => {
clearInterval(interval);
console.log("Timer stopped");
};
}, []);

return <h5 className="text-center">Time: {seconds}s</h5>;
}

? What Happens?

  • The timer increments seconds every second.
  • When the component is removed from the UI, clearInterval stops the timer.
  • Prevents memory leaks and unnecessary background work.

? Example 5: Fetching Data with useEffect

A very common use of useEffect is data fetching when the component first loads.

? View Code Example
// Fetch a user list once when the component is mounted
import React, { useState, useEffect } from "react";

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

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

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

? What Happens?

  • The effect runs once on mount and calls the sample API.
  • When the data arrives, setUsers updates state and the list of users is rendered.
  • This is a pattern you will use in almost every real-world React app.

? Tips & Best Practices

  • Always put cleanup logic in the return function inside useEffect to avoid memory leaks.
  • Use multiple useEffect hooks in the same component to separate concerns.
  • Do not ignore React’s dependency warnings—include all used variables in the dependency array.
  • If your effect does not depend on any changing value, use [] so it runs only once.
  • Keep effects as small and focused as possible for easier debugging and testing.

? Try It Yourself

  1. Create a timer component that starts automatically and stops using useEffect cleanup when unmounted.
  2. Fetch data from any public API and show a loading message while the request is in progress.
  3. Update the document.title to show the current count (e.g., "Messages (3)") using useEffect.
  4. Experiment with missing dependencies and observe the warnings that React shows in the console.

Goal: Use useEffect() to simulate React lifecycle behavior, manage side effects, and implement proper cleanup for stable, bug-free components.