๐Ÿš€ LEVEL UP TO SENIOR:Unlock 500+ Advanced Practical Challenges & Exercises.
๐ŸŽ“ COURSERA PARTNER:Earn professional Google, Meta, and IBM certificates to supercharge your resume.
REFERENCEreact

react Documentation

LOADING ENGINE...

useEffect

AI & DATA SCIENCE // useeffect

useEffect lets a functional component perform side effects, like data fetching, subscriptions, or manual DOM changes, after rendering has been committed to the DOM.

Syntax

useEffect(() => {
  // effect
  return () => { /* optional cleanup */ };
}, [dependencies]);

Deep Dive Course

useEffect runs its callback after React has committed the render to the DOM, making it the correct place for side effects that shouldn't happen directly during rendering โ€” the second argument, a dependency array, controls when the effect re-runs: omitting it entirely re-runs the effect after every render, an empty array [] runs it only once after the initial mount, and a populated array [a, b] re-runs it whenever any listed dependency changes between renders. Returning a function from the effect callback registers a cleanup function, which React calls right before the effect runs again, and once more when the component unmounts, making it the standard place to cancel subscriptions, clear timers, or otherwise undo whatever the effect set up.

1Understanding useEffect

useEffect runs its callback after React has committed the render to the DOM, making it the correct place for side effects that shouldn't happen directly during rendering โ€” the second argument, a dependency array, controls when the effect re-runs: omitting it entirely re-runs the effect after every render, an empty array [] runs it only once after the initial mount, and a populated array [a, b] re-runs it whenever any listed dependency changes between renders. Returning a function from the effect callback registers a cleanup function, which React calls right before the effect runs again, and once more when the component unmounts, making it the standard place to cancel subscriptions, clear timers, or otherwise undo whatever the effect set up.

๐Ÿ’ก

An empty dependency array, [], means an effect runs once on mount and its cleanup runs once on unmount โ€” but forgetting to include a variable the effect actually uses inside that array is one of the most common React bugs, since the effect then keeps referencing a stale, outdated value from the render when it was first created.

editor.html
import { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(id);
  }, []);
  return <p>Seconds: {seconds}</p>;
}
localhost:3000

2Practical Example

Here is a real-world application of useEffect showing how it is used in production React code.

editor.html
import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [name, setName] = useState(null);
  useEffect(() => {
    console.log('Fetching user', userId);
    fetchUser(userId).then(user => setName(user.name));
  }, [userId]);
  return <p>{name ?? 'Loading...'}</p>;
}
localhost:3000

3Best Practices

Follow these guidelines when working with useEffect:

1. Include every value from the component's scope that the effect actually reads inside the dependency array, rather than omitting some to avoid extra re-runs

2. Return a cleanup function from useEffect whenever the effect sets up something ongoing, like a subscription, timer, or event listener, that needs to be undone later

3. Use an empty dependency array, [], specifically for effects that should run exactly once, on mount, like an initial data fetch that doesn't depend on any changing value

โš ๏ธ

Tip: An empty dependency array, [], means an effect runs once on mount and its cleanup runs once on unmount โ€” but forgetting to include a variable the effect actually uses inside that array is one of the most common React bugs, since the effect then keeps referencing a stale, outdated value from the render when it was first created.

editor.html
import { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(id);
  }, []);
  return <p>Seconds: {seconds}</p>;
}
localhost:3000

4You Might Not Need an Effect

A value that can be computed directly from existing props or state โ€” a total, a filtered list, a formatted string โ€” should be a plain expression in the component body, not a separate piece of state kept in sync by useEffect. Syncing a derived value this way costs an extra render (the effect runs, calls a setter, and triggers a second render) and is one of the most common React anti-patterns.

๐Ÿ’ก

Before adding a useEffect, ask whether the value could just be a `const` computed during render. If the answer is yes, you don't need the effect.

editor.html
// Unnecessary
useEffect(() => setTotal(price * qty), [price, qty]);

// Just calculate it
const total = price * qty;
localhost:3000

5Race Conditions & the Ignore Flag

When an effect's dependency changes before an in-flight async request resolves, a slower, outdated response can arrive after a newer one and silently overwrite fresh state with stale data. Guard against this by tracking staleness with a local flag set inside the cleanup function, or by cancelling the previous request with an AbortController.

editor.html
useEffect(() => {
  let ignore = false;
  fetchUser(id).then(data => { if (!ignore) setUser(data); });
  return () => { ignore = true; };
}, [id]);
localhost:3000

6Objects, Arrays & Referential Equality

React compares each dependency to its previous value with Object.is, which checks objects and arrays by reference, not content. An object or array literal created inside the component body is a brand-new reference on every render, so listing it directly in a dependency array re-runs the effect on every render โ€” depend on the underlying primitive value instead.

โš ๏ธ

If an effect seems to re-run more often than its dependencies actually change, check whether one of those dependencies is an object or array literal recreated on every render.

editor.html
// New object every render, effect re-runs constantly
useEffect(() => {...}, [{ id }]);

// Depend on the primitive instead
useEffect(() => {...}, [id]);
localhost:3000

7Effects vs. Event Handlers

Code that responds to a specific user interaction โ€” a click, a form submission, a key press โ€” belongs in an event handler, not an effect. Event handlers fire because of a particular interaction; effects fire because the component rendered, or one of its dependencies changed, regardless of what (if anything) caused that change.

editor.html
<button onClick={handleSave}>Save</button>

// vs.
useEffect(() => { sync(); }, [state]);
localhost:3000

8The Golden Question

Before reaching for useEffect, ask one question: 'Am I synchronizing this component with something outside React?' If yes โ€” a browser API, a subscription, a timer, a network request โ€” useEffect is the right tool. If you're only calculating a value from existing props and state, or responding to a specific interaction, you almost certainly don't need an effect at all.

editor.html
// Am I syncing with something external?
// YES -> useEffect
// NO  -> calculate during render, or use an event handler
localhost:3000

Examples

Example 01Basic Usage
import { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(id);
  }, []);
  return <p>Seconds: {seconds}</p>;
}
Example 02Advanced Example
import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [name, setName] = useState(null);
  useEffect(() => {
    console.log('Fetching user', userId);
    fetchUser(userId).then(user => setName(user.name));
  }, [userId]);
  return <p>{name ?? 'Loading...'}</p>;
}
Example 03Guarding a Fetch Against Race Conditions
import { useState, useEffect } from 'react';

function UserProfile({ userId }) {
  const [name, setName] = useState(null);
  useEffect(() => {
    let ignore = false;
    fetchUser(userId).then(user => {
      if (!ignore) setName(user.name);
    });
    return () => { ignore = true; };
  }, [userId]);
  return <p>{name ?? 'Loading...'}</p>;
}

Best Practices

  • Include every value from the component's scope that the effect actually reads inside the dependency array, rather than omitting some to avoid extra re-runs
  • Return a cleanup function from useEffect whenever the effect sets up something ongoing, like a subscription, timer, or event listener, that needs to be undone later
  • Use an empty dependency array, [], specifically for effects that should run exactly once, on mount, like an initial data fetch that doesn't depend on any changing value
  • Calculate values that can be derived from existing props or state directly during render instead of storing them in state and syncing them with an effect
  • Guard async effects against race conditions with a local 'ignore' flag or an AbortController, since a dependency can change before an in-flight request resolves
  • Depend on primitive values rather than freshly-created object or array literals, since React compares dependencies by reference and a new literal is a new reference on every render

Interview Question

Why does useEffect's cleanup function run right before the effect runs again, not just when the component finally unmounts?โ–ผ

Hint: Think about what would happen if an effect set up a subscription tied to a changing dependency, like userId, and the old subscription was never cleaned up before a new one started.

If an effect sets up something tied to a specific dependency value, like subscribing to updates for a particular userId, and that dependency then changes, the effect needs to re-run to set up a fresh subscription for the new value โ€” but if the previous subscription, still pointing at the old userId, were left running, you'd end up with multiple simultaneous subscriptions accumulating over time, each one for a userId the component no longer even cares about, leaking resources and potentially causing stale, conflicting updates to fire. Running the cleanup function right before the effect re-runs ensures the old subscription, timer, or listener tied to the previous dependency value is properly torn down first, so exactly one active effect instance exists at a time, matching the component's current dependency values โ€” the same cleanup logic then naturally also runs one final time when the component unmounts entirely, since that's just another case of the effect's setup needing to be undone.

Exercises

MediumPractice using useEffect in a real scenario.
View Solution
import { useState, useEffect } from 'react';

function Timer() {
  const [seconds, setSeconds] = useState(0);
  useEffect(() => {
    const id = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(id);
  }, []);
  return <p>Seconds: {seconds}</p>;
}
EasyRewrite this component so `total` is calculated during render instead of being synced via useEffect.
View Solution
import { useState } from 'react';

function Cart({ price, qty }) {
  const total = price * qty; // no effect needed
  return <p>Total: {total}</p>;
}

Frequently Asked Questions

Why does useEffect's cleanup function run right before the effect runs again, not just when the component finally unmounts?โ–ผ

If an effect sets up something tied to a specific dependency value, like subscribing to updates for a particular userId, and that dependency then changes, the effect needs to re-run to set up a fresh subscription for the new value โ€” but if the previous subscription, still pointing at the old userId, were left running, you'd end up with multiple simultaneous subscriptions accumulating over time, each one for a userId the component no longer even cares about, leaking resources and potentially causing stale, conflicting updates to fire. Running the cleanup function right before the effect re-runs ensures the old subscription, timer, or listener tied to the previous dependency value is properly torn down first, so exactly one active effect instance exists at a time, matching the component's current dependency values โ€” the same cleanup logic then naturally also runs one final time when the component unmounts entirely, since that's just another case of the effect's setup needing to be undone.

Why not just call fetch() directly in the component body instead of inside useEffect?โ–ผ

A component's function body runs on every render. If a fetch call lived there directly, it would fire again on every re-render, and since a successful fetch typically calls a state setter, that update would trigger another render, which would trigger another fetch, and so on indefinitely. useEffect runs after rendering has committed, and its dependency array lets you control exactly when the fetch should re-run, keeping 'what the UI looks like' separate from 'when this side effect happens'.

Why does an effect sometimes re-run even though the value in its dependency array looks unchanged?โ–ผ

React compares each dependency to its previous value with Object.is, which checks objects and arrays by reference rather than by their contents. If the dependency is an object or array literal created inside the component body โ€” like [{ id }] instead of [id] โ€” a brand-new reference is produced on every render, so React sees it as 'changed' even when the underlying data is identical. Depending on the primitive value itself, or memoizing the object, fixes it.

Related Functions

usestatecomponentdidmountcomponentwillunmount