Side Effects
Side Effects
useEffect lets you perform side effects in functional components.
What are Side Effects?
- Data fetching (API calls)
- Subscriptions (WebSocket, timers)
- DOM manipulation
- Logging
- Anything that interacts with the outside world
Basic Usage
import { useEffect } from "react";
function App() {
const [data, setData] = useState(null);
// Runs after every render
useEffect(() => {
console.log("Component rendered");
});
// Runs only once on mount
useEffect(() => {
fetch("/api/data")
.then((res) => res.json())
.then((data) => setData(data));
}, []);
return <div>{data ? JSON.stringify(data) : "Loading..."}</div>;
}
When Effects Run
- No dependency array: After every render
- Empty dependency array
[]: Only on mount - With dependencies
[dep1, dep2]: When any dependency changes
Data Fetching
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
async function fetchUser() {
const response = await fetch(`/api/users/${userId}`);
const data = await response.json();
setUser(data);
}
fetchUser();
}, [userId]); // Re-fetch when userId changes
if (!user) return <div>Loading...</div>;
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
);
}
Dependency Array
Dependency Array
The dependency array tells React when to re-run the effect.
No Dependencies
// Runs after EVERY render
useEffect(() => {
console.log("Rendered");
});
Empty Dependencies
// Runs only ONCE on mount
useEffect(() => {
console.log("Mounted");
fetchInitialData();
}, []);
With Dependencies
// Runs when dependencies change
useEffect(() => {
fetchUserData(userId);
}, [userId]); // Re-runs when userId changes
Multiple Dependencies
useEffect(() => {
fetchFilteredData(category, sortBy);
}, [category, sortBy]); // Re-runs when either changes
Common Mistakes
// Bad: Missing dependency
useEffect(() => {
fetchData(userId); // userId is used but not in deps
}, []); // ESLint warning!
// Good: Include all dependencies
useEffect(() => {
fetchData(userId);
}, [userId]);
// Bad: Object in dependencies (creates new reference every render)
useEffect(() => {
fetchData(options); // options is a new object every render
}, [options]);
// Good: Destructure or use specific values
useEffect(() => {
fetchData({ page, limit });
}, [page, limit]);
ESLint Rule
The exhaustive-deps rule ensures you include all dependencies. Use it!
Cleanup Functions
Cleanup Functions
Return a function from useEffect to clean up resources.
Why Cleanup?
- Prevent memory leaks
- Cancel ongoing requests
- Remove event listeners
- Clear timers
Basic Cleanup
useEffect(() => {
const timer = setInterval(() => {
console.log("tick");
}, 1000);
// Cleanup function
return () => {
clearInterval(timer);
};
}, []);
Event Listeners
useEffect(() => {
const handleResize = () => {
console.log("Window resized");
};
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
API Requests
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
try {
const response = await fetch(`/api/users/${userId}`, {
signal: controller.signal
});
const data = await response.json();
setUser(data);
} catch (err) {
if (err.name !== "AbortError") {
setError(err.message);
}
}
}
fetchData();
return () => {
controller.abort(); // Cancel request on cleanup
};
}, [userId]);
WebSocket
useEffect(() => {
const ws = new WebSocket("wss://api.example.com");
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
setMessages((prev) => [...prev, data]);
};
return () => {
ws.close(); // Close WebSocket on cleanup
};
}, []);
Cleanup Timing
useEffect(() => {
// Setup runs after render
console.log("Effect ran");
return () => {
// Cleanup runs before next effect or unmount
console.log("Cleanup ran");
};
}, [dependency]);
Practice Problems
Create a reusable React component implementing useEffect. Include proper state management and accessibility.
Solution
// Production-ready component with:
// - Proper TypeScript types
// - Accessibility (ARIA)
// - Error boundaries
// - Loading states
// - Memoization where neededWrite unit and integration tests for useEffect using React Testing Library.
Solution
// Test coverage:
// 1. Rendering tests
// 2. Interaction tests
// 3. Edge case tests
// 4. Accessibility testsOptimize useEffect for performance. Consider memoization, code splitting, and bundle size.
Solution
// Optimization techniques:
// 1. React.memo / useMemo / useCallback
// 2. Code splitting with lazy()
// 3. Virtual scrolling for lists
// 4. Image lazy loading
// 5. Bundle analysisQuiz
1. When does useEffect run with no dependency array?
2. What is the purpose of the cleanup function?
3. When should you include a value in the dependency array?
4. How do you fetch data only once?
Flashcards
Question
What is useEffect used for?
Click to reveal answer
Answer
Performing side effects in functional components
Question
What does an empty dependency array do?
Click to reveal answer
Answer
Makes the effect run only once on mount
Question
What is a cleanup function?
Click to reveal answer
Answer
A function returned from useEffect to clean up resources
Question
When does cleanup run?
Click to reveal answer
Answer
Before the next effect runs and on unmount
Question
What is useEffect?
Click to reveal answer
Answer
useEffect is a key concept in frontend development.
Revision Notes
Key Takeaways
- 1.useEffect handles side effects
- 2.Dependency array controls when effect runs
- 3.Cleanup functions prevent memory leaks
- 4.Include all used values in dependencies
- 5.Use AbortController for cancellable requests
Interview Tips
- •Explain when effects run with different dependency arrays
- •Show how to clean up resources
- •Demonstrate data fetching with useEffect
Cheat Sheet
Cheat Sheet
Basic Syntax
useEffect(() => {
// side effect
return () => {
// cleanup
};
}, [dependencies]);
Dependency Patterns
useEffect(() => {}); // every render
useEffect(() => {}, []); // once on mount
useEffect(() => {}, [dep]); // when dep changes
Cleanup
useEffect(() => {
const timer = setInterval(...);
return () => clearInterval(timer);
}, []);
Data Fetching
useEffect(() => {
const controller = new AbortController();
fetch(url, { signal: controller.signal });
return () => controller.abort();
}, [url]);