The useEffect
hook is one of the most powerful and essential features in React. It enables functional components to handle side effects like data fetching, subscriptions, DOM manipulation, and timers. However, improper use can lead to performance issues, memory leaks, or even infinite loops.
In this module, we’ll explore useEffect
in depth: when to use it, how to control its behavior, how dependency arrays work, and how to manage cleanup.
Table of Contents
- What Are Side Effects in React?
- Introduction to
useEffect
- Basic Syntax of
useEffect
- Dependency Array Explained
- Common Use Cases for
useEffect
- Cleanup Functions in
useEffect
- Avoiding Infinite Loops
- Best Practices
- Code Examples
- Conclusion
1. What Are Side Effects in React?
Side effects are operations that affect things outside the scope of the component’s render cycle. These include:
- Fetching data from APIs
- Manipulating the DOM
- Setting up subscriptions or event listeners
- Working with timers (e.g.,
setInterval
) - Interacting with browser storage (
localStorage
,sessionStorage
)
React is declarative and predictable, so side effects are intentionally isolated using useEffect
.
2. Introduction to useEffect
React’s useEffect
hook lets you perform side effects in functional components. It essentially replaces the behavior of lifecycle methods like componentDidMount
, componentDidUpdate
, and componentWillUnmount
in class components.
3. Basic Syntax of useEffect
jsxCopyEditimport { useEffect } from 'react';
useEffect(() => {
// Side effect code here
});
By default, useEffect
runs after every render. But its behavior can be customized using a second argument: the dependency array.
4. Dependency Array Explained
jsxCopyEdituseEffect(() => {
// Side effect
}, [dependency1, dependency2]);
[]
— empty array: run only once (on mount)[value]
— run only whenvalue
changes- No array: run after every render
Example:
jsxCopyEdituseEffect(() => {
console.log("Component mounted");
}, []);
jsxCopyEdituseEffect(() => {
console.log(`Count changed: ${count}`);
}, [count]);
5. Common Use Cases for useEffect
- Fetching Data
jsxCopyEdituseEffect(() => {
fetch('/api/data')
.then(res => res.json())
.then(setData);
}, []);
- Event Listeners
jsxCopyEdituseEffect(() => {
const handleResize = () => console.log(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
- Timers
jsxCopyEdituseEffect(() => {
const timer = setTimeout(() => alert("Hello!"), 3000);
return () => clearTimeout(timer);
}, []);
6. Cleanup Functions in useEffect
If your side effect returns something (a function), React uses it to clean up before the component unmounts or the effect re-runs.
jsxCopyEdituseEffect(() => {
const intervalId = setInterval(() => {
console.log('Tick');
}, 1000);
return () => {
clearInterval(intervalId); // cleanup
};
}, []);
Cleanup is essential when dealing with intervals, subscriptions, or event listeners to avoid memory leaks.
7. Avoiding Infinite Loops
A common pitfall is forgetting to manage dependencies correctly:
jsxCopyEdituseEffect(() => {
setCount(count + 1); // BAD: causes infinite loop
}, [count]);
Instead, use function updates or state-only changes when needed:
jsxCopyEdituseEffect(() => {
setCount(prev => prev + 1);
}, []);
8. Best Practices
- Always include all dependencies your effect uses.
- Use ESLint rules like
react-hooks/exhaustive-deps
to detect missing dependencies. - Separate unrelated side effects into separate
useEffect
calls. - Avoid modifying state within
useEffect
without conditions—this can lead to re-render loops.
9. Code Examples
Fetching Data with Cleanup
jsxCopyEditimport { useState, useEffect } from 'react';
function Posts() {
const [posts, setPosts] = useState([]);
useEffect(() => {
let isMounted = true;
fetch('https://jsonplaceholder.typicode.com/posts')
.then(res => res.json())
.then(data => {
if (isMounted) setPosts(data);
});
return () => {
isMounted = false;
};
}, []);
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}
10. Conclusion
The useEffect
hook is indispensable for any React developer working with dynamic behavior, asynchronous data, or cleanup logic. Mastering it enables you to handle lifecycle-like behavior in functional components cleanly and efficiently.