iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

useEffect

useEffect runs side effects after the DOM commits. Subscriptions, timers, manual data fetches, document title updates — anything that touches the world outside React goes in useEffect.

Effects + cleanup + deps

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

// 1) Run on every render — RARELY what you want
useEffect(() => {
    console.log('every render');
});

// 2) Run once (on mount), cleanup on unmount — most common shape
useEffect(() => {
    const id = setInterval(() => tick(), 1000);
    return () => clearInterval(id);    // cleanup
}, []);

// 3) Re-run when a dependency changes
useEffect(() => {
    const ctrl = new AbortController();
    fetch(`/api/posts/${postId}`, { signal: ctrl.signal })
        .then((r) => r.json())
        .then(setPost)
        .catch((e) => { if (e.name !== 'AbortError') setError(e); });
    return () => ctrl.abort();
}, [postId]);

// 4) Subscribe to an external store
useEffect(() => {
    const unsub = store.subscribe(setSnapshot);
    return unsub;
}, []);

// 5) Listen to a window event
useEffect(() => {
    const onResize = () => setW(window.innerWidth);
    window.addEventListener('resize', onResize);
    return () => window.removeEventListener('resize', onResize);
}, []);

// 6) Update document.title — synced to a render value
useEffect(() => {
    document.title = `${unread} unread`;
}, [unread]);

// 7) Async effect — declare an inner async function
useEffect(() => {
    let active = true;
    (async () => {
        const data = await api.search(query);
        if (active) setResults(data);
    })();
    return () => { active = false; };
}, [query]);

// 8) Common bugs + fixes

// 8a) Missing dep — stale closure
// ❌
useEffect(() => { fetchUser(userId); }, []);     // re-uses initial userId
// ✅
useEffect(() => { fetchUser(userId); }, [userId]);

// 8b) Unstable object/function deps re-trigger every render
// ❌
useEffect(() => { ... }, [{ id }]);
// ✅
useEffect(() => { ... }, [id]);                  // primitive
const cb = useCallback(() => doThing(id), [id]);
useEffect(() => { cb(); }, [cb]);

// 9) Strict Mode runs effects twice in dev — use cleanup correctly and it's harmless

// 10) When NOT to use useEffect
//   - Derived values → useMemo / compute during render
//   - Event handlers → put the work in the handler, not in an effect
//   - Data fetching → prefer a library (TanStack Query, SWR) — they handle race, cache, retry

Why it matters

useEffect is for “sync to the world.” If a value can be computed during render, do that — effects are how bugs sneak in.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
useEffect(() => {
    document.title = `Count: ${count}`;
    return () => { /* cleanup */ };
}, [count]);
Try it Yourself »

Exercise

Side effects hook.

(() => { /* fetch */ }, []);

Test yourself

Q1. useEffect runs…
Q2. Empty deps array means…
Q3. Cleanup is performed by…

Discussion

Loading…