Frontend Master

JavaScript Curriculum

useEffect
+60 XP

useEffect

medium
~35 min·60 XP

The coffee shop menu needs to fetch items on load, update the page title when the category changes, and show a live order countdown timer. All three are side effects — things React doesn't handle automatically.

useEffect

useEffect is how you synchronise React components with the outside world — APIs, the DOM, timers, subscriptions. It runs after render, not during.

Four Firing Patterns

The dependency array controls everything:

useEffect-visualizer.jsx

Empty dependency array — runs once after the first render. Perfect for fetching initial data, setting up subscriptions, or reading from localStorage.

After first render only
useEffect(() => {
  // Runs ONCE after first render
  fetchMenuItems().then(setItems)
  document.title = 'The Grind — Menu'
}, [])  // ← empty array = mount only
← Mounted: effect ran once ✓
// Effect log will appear here
⚠️useEffect is not a lifecycle method
Do not think of useEffect as componentDidMount + componentDidUpdate + componentWillUnmount. Think of it as: "after render, synchronise this external thing with this state". The dependency array says which state it synchronises with.

Dependency Array Rules

Every dep mistake becomes a stale closure bug or an infinite loop:

dependency-array.jsx

Every value used inside the effect that comes from the component scope must be in the dependency array. Missing deps cause stale closure bugs.

❌ Wrong
// ❌ Missing 'userId' in deps — stale closure!
useEffect(() => {
  fetchProfile(userId) // uses userId
  setTitle(`Profile: ${name}`) // uses name
}, []) // neither listed — always uses initial values
✓ Correct
// ✓ All used values listed
useEffect(() => {
  fetchProfile(userId)
  setTitle(`Profile: ${name}`)
}, [userId, name]) // re-runs when either changes
💡React's exhaustive-deps lint rule
Install `eslint-plugin-react-hooks` and enable the `exhaustive-deps` rule. It will highlight every missing dependency automatically. Never silence the warning — fix the root cause instead.

Cleanup Patterns — Four Real Scenarios

Timers, fetch abort, event listeners, and subscriptions:

cleanup-patterns.jsx

Always clear timers when the component unmounts. Without cleanup, the callback fires on an unmounted component — causing a memory leak and possible state update errors.

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

  // Cleanup: clear the timer when:
  // 1. Component unmounts
  // 2. Effect re-runs (deps changed)
  return () => clearInterval(timerId)
}, []) // runs once, cleans up on unmount
Live demo — timer
0s
stopped
// Click Start to see timer cleanup
ℹ️When cleanup runs
The cleanup function runs in two situations: when the component unmounts, and before the effect runs again (because deps changed). This means cleanup always pairs with setup — every effect that sets something up should tear it down.

Your Challenge

Write a useMenuItems(search) pattern inline: fetch /api/menu?q=${search} on mount and when search changes. Use AbortController — abort in cleanup. Show loading state while fetching. Add document.title = \Menu — ${search || 'All'}`in a separate effect. Add asetInterval` ticker that counts seconds since mount — clear it on unmount.

Challenge

Write a component that fetches menu items on mount (empty deps). Add a search term state — re-fetch when it changes using AbortController for cleanup. Sync document.title with the current category. Show a 30-second countdown that clears on unmount.

useEffectdependency-arraycleanupmountunmountAbortControllersetIntervalclearIntervaladdEventListenerremoveEventListenerstale-closure
useEffect | Nexus Learn