🎓 What You Will Learn
- What is useEffect: Handle side effects in functional components
- Run Once on Mount: Use empty dependency array
[] - Dependencies: Control when effect runs
- Cleanup Functions: Clean up resources and listeners
- Data Fetching: Fetch API data when component mounts
- Multiple Effects: Use many useEffect calls
- Common Patterns: Timers, listeners, API calls
- Best Practices: Avoid common mistakes
What is useEffect?
useEffect is a React Hook that lets you perform side effects in functional components. Side effects are operations that interact with the outside world: fetching data, updating the DOM, setting up timers, listening to events, etc.
Without useEffect, side effects would run every time the component renders, causing performance issues and bugs. useEffect lets you control when side effects run.
- • Fetching data from APIs
- • Setting up event listeners
- • Updating the document title
- • Starting timers/intervals
- • Saving to localStorage
Basic Syntax
import { useEffect } from 'react';
export function MyComponent() {
useEffect(() => {
// Side effect code here
console.log("Effect ran!");
// Optional cleanup function
return () => {
console.log("Cleanup!");
};
}, []); // Dependency array
//^ Controls when effect runs
return <p>Component</p>;
}Effect runs ONE time when component mounts:
Logs will appear here...
👉 Click the button multiple times. The effect only logged once on mount!
import { useState, useEffect } from 'react';
export function EffectOnce() {
const [count, setCount] = useState(0);
const [logs, setLogs] = useState([]);
// Runs ONCE when component mounts
useEffect(() => {
setLogs(prev => [...prev, 'Effect ran!']);
}, []); // Empty dependency array = run once
return (
<>
<button onClick={() => setCount(count + 1)}>
Click ({count})
</button>
{logs.map((log, i) => <p key={i}>{log}</p>)}
</>
);
}The Dependency Array
The dependency array controls when useEffect runs:
| Dependency Array | When It Runs | Use Case |
|---|---|---|
| No array provided | Every render | ❌ Usually wrong, causes bugs |
| Empty <Code>[]</Code> | Once on mount | ✅ Initialize, fetch data |
| <Code>[count]</Code> | When count changes | ✅ React to specific state |
| <Code>[count, text]</Code> | When count or text changes | ✅ Multiple dependencies |
Effect runs when count changes:
👉 Type in the message input. Effect doesn't run! Change count, it runs!
import { useState, useEffect } from 'react';
export function DependentEffect() {
const [count, setCount] = useState(0);
const [message, setMessage] = useState("");
// Runs when COUNT changes (not when message changes)
useEffect(() => {
console.log("Count changed:", count);
}, [count]); // Dependency array
return (
<>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<input
value={message}
onChange={(e) => setMessage(e.target.value)}
/>
</>
);
}Cleanup Functions
Return a function from useEffect to clean up resources. This runs when:
- • The component unmounts
- • Before the effect runs again
useEffect(() => {
const timer = setInterval(() => {
console.log("Tick");
}, 1000);
// Cleanup function
return () => {
clearInterval(timer); // Clean up timer
};
}, []);Event listener with cleanup:
❌ Listener OFF
👉 Turn ON and press any key. Turn OFF to remove listener. Check console!
import { useState, useEffect } from 'react';
export function Listener() {
const [isOn, setIsOn] = useState(false);
useEffect(() => {
if (!isOn) return;
const handleKey = () => console.log("Key pressed!");
// Setup
window.addEventListener("keypress", handleKey);
// Cleanup function - runs when component unmounts
// or when isOn becomes false
return () => {
window.removeEventListener("keypress", handleKey);
console.log("Listener removed!");
};
}, [isOn]);
return (
<button onClick={() => setIsOn(!isOn)}>
{isOn ? "Turn OFF" : "Turn ON"}
</button>
);
}Multiple useEffect Calls
Use multiple useEffect calls for different concerns. Each effect handles one thing.
export function MyComponent() {
// Effect 1: Fetch data
useEffect(() => {
fetch('/api/data');
}, []);
// Effect 2: Set up event listener
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
// Effect 3: Sync to localStorage
useEffect(() => {
localStorage.setItem('name', name);
}, [name]);
}Each effect has its own dependency:
👉 Change count (Effect 1 runs). Change text (Effect 2 runs).
import { useState, useEffect } from 'react';
export function MultipleEffects() {
const [count, setCount] = useState(0);
const [text, setText] = useState("");
// Effect 1: Runs when count changes
useEffect(() => {
console.log("Count changed:", count);
}, [count]);
// Effect 2: Runs when text changes
useEffect(() => {
console.log("Text changed:", text);
}, [text]);
return (
<>
<input
type="number"
value={count}
onChange={(e) => setCount(Number(e.target.value))}
/>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
/>
</>
);
}Fetching Data from APIs
The most common use of useEffect is fetching data when the component mounts.
export function UserList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => {
setUsers(data);
setLoading(false);
})
.catch(err => {
setError(err);
setLoading(false);
});
}, []); // Run once on mount
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}Simulated API fetch:
👉 Click different user buttons. Effect fetches data when id changes!
import { useState, useEffect } from 'react';
export function UserFetcher() {
const [id, setId] = useState(1);
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
setLoading(true);
// Fetch user data
fetch(`/api/users/${id}`)
.then(res => res.json())
.then(data => {
setUser(data);
setLoading(false);
});
}, [id]); // Re-fetch when id changes
return (
<>
<button onClick={() => setId(1)}>User 1</button>
<button onClick={() => setId(2)}>User 2</button>
{loading && <p>Loading...</p>}
{user && <p>{user.name}</p>}
</>
);
}Common Patterns
useEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(data => setData(data));
}, []); // Empty dependency = fetch onceuseEffect(() => {
fetch(`/api/users/${id}`)
.then(res => res.json())
.then(data => setUser(data));
}, [id]); // Re-fetch when id changesuseEffect(() => {
const handleResize = () => {
console.log('Window resized');
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);Change browser tab title:
Current Title: Welcome - My App
👉 Click buttons and watch the browser tab title change!
import { useState, useEffect } from 'react';
export function TabTitle() {
const [title, setTitle] = useState("Welcome");
useEffect(() => {
document.title = title;
}, [title]);
return (
<>
<button onClick={() => setTitle("Settings")}>
Settings
</button>
<p>Current: {title}</p>
</>
);
}Best Practices
Common Mistakes
// ❌ BAD: Runs every render = infinite loop
useEffect(() => {
fetch('/api/data');
// Missing dependency array!
});
// ✅ GOOD: Runs once on mount
useEffect(() => {
fetch('/api/data');
}, []);// ❌ BAD: count used but not in dependencies
useEffect(() => {
console.log(count);
}, []); // Missing count!
// ✅ GOOD: Include all dependencies
useEffect(() => {
console.log(count);
}, [count]);// ❌ BAD: Memory leak - listener never removed
useEffect(() => {
window.addEventListener('resize', handleResize);
}, []);
// ✅ GOOD: Clean up listeners
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
};
}, []);What's Next?
- Custom Hooks: Build reusable effect logic (useFetch, useLocalStorage)
- useCallback & useMemo: Optimize useEffect dependencies
- useContext: Share data without prop drilling
- useReducer: Complex state logic in effects
- API Libraries: Axios, React Query, SWR for better data fetching
Experiment with the examples! 💪 useEffect is powerful but takes practice to master. Build apps with API calls, listeners, and timers to solidify your understanding!