Lesson 4 of 12
useEffect & Side Effects
You will learn how to use `useEffect` for work outside rendering, such as fetching data, timers and subscriptions, and how to clean it up. Getting this right avoids leaks and duplicate .
import { useState, useEffect } from 'react'
function UserProfile({ userId }) {
const [user, setUser] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
// The dependency array [userId] means:
// "run this effect when userId changes"
let cancelled = false // cleanup flag
setLoading(true)
setError(null)
fetch(`/api/users/${userId}`)
.then(res => {
if (!res.ok) throw new Error("Not found")
return res.json()
})
.then(data => {
if (!cancelled) setUser(data)
})
.catch(err => {
if (!cancelled) setError(err)
})
.finally(() => {
if (!cancelled) setLoading(false)
})
// Cleanup function — runs before next effect or on unmount
return () => { cancelled = true }
}, [userId]) // dependency array
if (loading) return <p>Loading...</p>
if (error) return <p>Error: {error.message}</p>
return <h1>{user.name}</h1>
}import { useState, useEffect, useRef } from 'react'
// useEffect dependency array patterns:
// [] — run once after mount
// [a, b] — run when a or b changes
// (omitted) — run after every render (usually wrong)
// Timer example — with proper cleanup
function Stopwatch() {
const [seconds, setSeconds] = useState(0)
const [running, setRunning] = useState(false)
useEffect(() => {
if (!running) return
const id = setInterval(() => setSeconds(s => s + 1), 1000)
return () => clearInterval(id) // cleanup clears the interval
}, [running])
return (
<div>
<p>{seconds}s</p>
<button onClick={() => setRunning(r => !r)}>
{running ? "Pause" : "Start"}
</button>
<button onClick={() => { setRunning(false); setSeconds(0) }}>Reset</button>
</div>
)
}
// useRef — mutable ref that doesn't trigger re-renders
function AutoFocusInput() {
const inputRef = useRef(null)
useEffect(() => {
inputRef.current?.focus() // focus on mount
}, [])
return <input ref={inputRef} placeholder="I'm focused!" />
}Common question
Does every piece of logic belong in useEffect?
An effect keeps a component in sync with something outside React. Its the values that, when they change, mean it needs to sync again; its returned cleanup undoes the previous sync. That is the whole model, and it explains how every effect you write will behave.
Before writing one, ask what outside thing you are syncing with. If the answer is "nothing, I'm working out a value", compute it during render. If it is "responding to a click", put it in the event handler. Save effects for subscriptions, timers, manual work and data fetching, and always clean them up.
Source: The React documentation has a whole page called "You Might Not Need an Effect", listing common cases where an effect is the wrong tool.
Common mix-ups
Each of these sounds reasonable. Think about why it is wrong, then open the explanation.
An effect runs after render, so it's the natural place for anything that should happen when data changes.
An empty dependency array means the effect never runs.
Cleanup functions are optional tidiness.
Tip: use the ← and → keys to move between lessons.