14_useEffect Hook - Maniconserve/React-Wiki GitHub Wiki
useEffect lets you run side effects in a functional component. A side effect is anything that happens outside of rendering — fetching data, setting up a timer, updating the document title, adding event listeners, etc.
import { useEffect } from 'react';
useEffect(() => {
// side effect code here
}, [dependencies]);
| Part | What it is |
|---|---|
| First argument | A function containing your side effect code |
| Second argument | Dependency array — controls when the effect runs |
useEffect lets you run side effects in a functional component. A side effect is anything that happens outside of rendering — fetching data, setting up a timer, updating the document title, adding event listeners, etc.
import { useEffect } from 'react';
useEffect(() => {
// side effect code here
}, [dependencies]);| Part | What it is |
|---|---|
| First argument | A function containing your side effect code |
| Second argument | Dependency array — controls when the effect runs |
The behaviour of useEffect is entirely controlled by the dependency array — the second argument.
Runs after every single render — the initial render and every re-render after that.
import { useState, useEffect } from 'react';
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
useEffect(() => {
console.log('Effect ran — component rendered or re-rendered');
}); // ← no second argument at all
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<input value={name} onChange={e => setName(e.target.value)} placeholder="Type name" />
</div>
);
}Every time count changes OR name changes — the effect runs. This is rarely what you want and can cause performance issues.
When to use: Almost never. Usually an oversight. If you find yourself using this, you likely need one of the other two forms.
Runs only once — after the very first render. Never runs again no matter how many times the component re-renders.
import { useState, useEffect } from 'react';
function App() {
const [user, setUser] = useState(null);
useEffect(() => {
console.log('Runs only once — on mount');
// perfect place for:
// - fetching initial data from an API
// - setting up a timer
// - adding event listeners
fetch('https://jsonplaceholder.typicode.com/users/1')
.then(res => res.json())
.then(data => setUser(data));
}, []); // ← empty array
return (
<div>
{user ? <p>Hello, {user.name}!</p> : <p>Loading...</p>}
</div>
);
}When to use: Fetching data when the page loads, setting up subscriptions, initialising third-party libraries.
This is the equivalent of
componentDidMountin class components.
Runs after the first render and again whenever any value in the dependency array changes.
import { useState, useEffect } from 'react';
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
useEffect(() => {
console.log('count changed to:', count);
document.title = `Count is ${count}`;
}, [count]); // ← runs only when 'count' changes
// This effect only runs when 'name' changes
useEffect(() => {
console.log('name changed to:', name);
}, [name]); // ← runs only when 'name' changes
return (
<div>
<button onClick={() => setCount(count + 1)}>Count: {count}</button>
<input value={name} onChange={e => setName(e.target.value)} placeholder="Type name" />
</div>
);
}- Clicking the button changes
count→ first effect runs, second does not - Typing in the input changes
name→ second effect runs, first does not
When to use: React to a specific state or prop change — refetching data when a search term changes, updating the document title when a value changes, etc.
// Runs after EVERY render
useEffect(() => {
console.log('runs every render');
});
// Runs ONCE after first render only
useEffect(() => {
console.log('runs once on mount');
}, []);
// Runs on first render + whenever 'count' changes
useEffect(() => {
console.log('runs when count changes');
}, [count]);| Dependency Array | When does it run? |
|---|---|
| No array | After every render |
[] empty array |
Once — after first render only |
[value] with values |
After first render + when any listed value changes |
Some effects need to be cleaned up when the component is removed from the screen — timers need clearing, event listeners need removing, subscriptions need cancelling. Otherwise they keep running in the background and cause memory leaks.
Return a function from inside useEffect — React runs it when the component unmounts (or before running the effect again).
import { useState, useEffect } from 'react';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds(prev => prev + 1);
}, 1000);
// cleanup — clear the interval when component unmounts
return () => {
clearInterval(interval);
console.log('Timer cleared');
};
}, []); // runs once — sets up timer on mount, clears on unmount
return <p>Seconds: {seconds}</p>;
}useEffect(() => {
function handleResize() {
console.log('Window width:', window.innerWidth);
}
window.addEventListener('resize', handleResize);
// cleanup — remove listener when component unmounts
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);You can use useEffect as many times as needed — each handles a separate concern. This keeps related logic together.
function UserPage({ userId }) {
const [user, setUser] = useState(null);
const [posts, setPosts] = useState([]);
// Effect 1 — fetch user whenever userId changes
useEffect(() => {
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => setUser(data));
}, [userId]);
// Effect 2 — fetch posts whenever userId changes
useEffect(() => {
fetch(`/api/users/${userId}/posts`)
.then(res => res.json())
.then(data => setPosts(data));
}, [userId]);
// Effect 3 — update page title whenever user changes
useEffect(() => {
if (user) document.title = `${user.name}'s Profile`;
}, [user]);
return <div>...</div>;
}If you have seen class components, useEffect replaces three lifecycle methods:
| Class Component | useEffect Equivalent |
|---|---|
componentDidMount |
useEffect(() => { }, []) — empty array |
componentDidUpdate |
useEffect(() => { }, [value]) — with dependency |
componentWillUnmount |
return () => { } — cleanup function |
// Class component
componentDidMount() {
fetch('/api/data').then(...);
}
componentWillUnmount() {
clearInterval(this.timer);
}
// Functional equivalent — both in one useEffect
useEffect(() => {
fetch('/api/data').then(...);
return () => clearInterval(timer); // cleanup
}, []);| Goal | Code |
|---|---|
| Run once on mount | useEffect(() => { }, []) |
| Run on every render | useEffect(() => { }) |
| Run when value changes | useEffect(() => { }, [value]) |
| Run when multiple values change | useEffect(() => { }, [a, b, c]) |
| Cleanup on unmount |
return () => { cleanup here } inside useEffect |
// ❌ Missing dependency — effect uses 'count' but doesn't list it
useEffect(() => {
console.log(count); // reads count but count is not in deps
}, []);
// ✅ Correct — list every value the effect uses
useEffect(() => {
console.log(count);
}, [count]);
// ❌ Fetching data without [] — causes infinite loop
// setUser triggers a re-render → effect runs again → setUser again → loop
useEffect(() => {
fetch('/api/user').then(res => res.json()).then(data => setUser(data));
}); // no array!
// ✅ Correct
useEffect(() => {
fetch('/api/user').then(res => res.json()).then(data => setUser(data));
}, []);