βοΈ React Hook: useEffect
β
π Quick Summary β
useEffect
lets you perform side effects in functional components.- Side effects = things outside Reactβs rendering process (e.g., fetching data, DOM manipulation, subscriptions).
- Syntax:tsx
useEffect(() => { // effect code return () => { // optional cleanup }; }, [dependencies]);
π§ Mental Model β
- Think of
useEffect
as Reactβs way of saying:
βRun this code after the render and again whenever dependencies change.β - Cleanup runs like housekeeping π§Ή before the effect re-runs or when the component unmounts.
π Key Concepts β
Default Behavior
- Runs after every render if no dependency array is provided.
Dependency Array
- Controls when the effect runs.
[]
β run only once (on mount).[var1, var2]
β re-run whenevervar1
orvar2
changes.
Cleanup Function
- Return a function from
useEffect
to clean up resources (e.g., event listeners, timers). - Runs before component unmount or before the effect re-runs.
- Return a function from
Multiple Effects
- A component can have many
useEffect
hooks. - Each handles a separate concern (e.g., data fetch, subscription).
- A component can have many
Execution Order
- Effects run after the DOM is updated.
- Cleanup runs before next effect execution.
π» Code Examples β
Example 1: Run Once on Mount β
import { useEffect } from "react";
export default function Hello() {
useEffect(() => {
console.log("Component mounted");
}, []);
return <h1>Hello World</h1>;
}
How it works (stepβbyβstep):
- Component renders the first time.
- React commits DOM updates.
- Because the dependency array is
[]
, the effect runs once after mount and logs the message. - It will not run again unless the component unmounts/remounts.
Example 2: Re-run on Dependency Change β
import { useState, useEffect } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log(`Count updated: ${count}`);
}, [count]);
return (
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
);
}
How it works (stepβbyβstep):
- On initial render, effect runs once after paint and logs with
count = 0
. - Clicking the button updates
count
β triggers a re-render. - After React updates the DOM, the effect runs again because
count
in the dependency array changed. - The log always reflects the latest
count
value.
Example 3: Cleanup with Event Listener β
import { useEffect } from "react";
export default function WindowResizeLogger() {
useEffect(() => {
const handleResize = () => console.log("Window width:", window.innerWidth);
window.addEventListener("resize", handleResize);
// Cleanup on unmount
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
return <p>Resize the window and check the console</p>;
}
How it works (stepβbyβstep):
- On mount, effect adds a
resize
event listener. - The listener logs width whenever the window resizes.
- When the component unmounts, React calls the cleanup to remove the listener, preventing leaks.
- If the effect reβran (with deps), cleanup would run before setting the new listener.
Example 4: Fetching Data β
import { useState, useEffect } from "react";
export default function Users() {
const [users, setUsers] = useState([]);
useEffect(() => {
async function fetchData() {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await res.json();
setUsers(data);
}
fetchData();
}, []);
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}
How it works (stepβbyβstep):
- Initial render shows an empty list.
- After mount, the effect runs and kicks off the async
fetch
. - When the data arrives,
setUsers
updates state β triggers a re-render. - The list renders with the fetched users.
β οΈ Common Pitfalls & Gotchas β
- β Forgetting the dependency array β effect runs after every render (may cause infinite loops).
- β Incorrect dependencies β stale data or missed updates.
- β Running expensive operations without memoization.
- β Forgetting cleanup β memory leaks from unremoved listeners/timers.
β Best Practices β
- Always declare dependencies explicitly in the array.
- Extract reusable logic into custom hooks.
- Keep effects focused β one effect per concern.
- Use cleanup to prevent leaks.
- Use libraries like React Query for complex data fetching.
β Interview Q&A β
Q1. What is useEffect
used for?
A: To handle side effects in React functional components (e.g., API calls, subscriptions, timers, DOM manipulation).
Q2. Difference between componentDidMount
, componentDidUpdate
, and componentWillUnmount
vs useEffect
?
A: In class components, those lifecycle methods are separate. In functional components, useEffect
combines them:
[]
βcomponentDidMount
[deps]
βcomponentDidUpdate
return () => {}
βcomponentWillUnmount
Q3. Why does useEffect
run after render?
A: To ensure the DOM is updated first, so side effects donβt block the UI rendering.
Q4. How does cleanup work in useEffect
?
A: Return a function from the effect. React calls it before unmounting or before re-running the effect.
Q5. What happens if we omit the dependency array?
A: The effect runs after every render, which may lead to performance issues or infinite loops.
Q6. How to prevent infinite loops with useEffect
?
A: Ensure dependency arrays are correct. Donβt include non-stable references (like inline functions/objects) unless memoized.
Q7. Can we use multiple useEffect
hooks in a component?
A: Yes, React encourages splitting unrelated logic into separate effects.