Day 4 of #100DaysOfCode — Mastering useEffect in React
Source: Dev.to
Understanding useEffect in React
“Unlock a magical spellbook: summon APIs, tame event listeners, and command the DOM.”
useEffect is the key to handling side effects, keeping your UI consistent, and writing logic that reacts to data changes. Below is a clear, practical breakdown of why it exists, how it works, and how to avoid dreaded infinite loops.
What Are Side Effects in React?
Side effects are any actions your component performs outside the normal rendering process.
React’s render cycle must remain predictable and pure—but many real‑world tasks aren’t pure at all, such as:
- Fetching data from an API
- Updating
document.title - Working with timers (
setTimeout,setInterval) - Using browser APIs like
localStorage - Adding event listeners
- Subscribing to WebSockets
These actions affect the world outside your component—those are side effects.
🤔 Why Do React Components Need useEffect?
Isn’t useState enough?
| Hook | Purpose |
|---|---|
useState | Updates your UI. |
useEffect | Handles everything outside your UI (side effects). |
If React allowed side effects during rendering, you’d get unpredictable behavior and potentially infinite loops. React must render → compare → update in a pure, deterministic way.
useEffect runs after React paints the screen, keeping the render pure and safe.
Three Types of useEffect Behavior
1️⃣ No Dependency Array – Runs on every render
useEffect(() => {
console.log("I run after every render");
});
When to use: Rarely. This triggers on initial mount + every re‑render, which can cause performance issues or infinite loops.
2️⃣ Empty Dependency Array [] – Runs only once (on mount)
useEffect(() => {
console.log("I run only once when the component mounts");
}, []);
When to use:
- Fetch API data on mount
- Set up listeners, subscriptions, or timers once
- Initialize state from
localStorage
This mimics componentDidMount in class components.
3️⃣ Dependency‑Based – Runs when dependencies change
useEffect(() => {
console.log("I run when count changes");
}, [count]);
When to use:
- Syncing state with props
- Re‑fetching when filters change
- Updating UI or document title
- Running logic when a specific value updates
This is the most common and most powerful usage.
How Does the Dependency Array Actually Work?
The array tells React:
“Run this effect only if any of these values change from the previous render.”
React compares each dependency with its previous value using shallow comparison.
useEffect(() => {
console.log("Runs when userId changes");
}, [userId]);
If userId goes from 1 → 2, the effect runs.
Important:
- Objects, arrays, and functions always get a new reference unless memoized (
useMemo,useCallback). - This can cause unintentionally frequent effect runs.
How to Avoid Infinite Loops in useEffect
The most common cause
useEffect(() => {
setCount(count + 1);
}, [count]);
What happens?
setCounttriggers a re‑render.countchanges → dependency array detects a change.- Effect runs again → state updates again → loop forever.
How to prevent it
- Don’t update state inside an effect that depends on that same state.
- Use functional updates when you need to increment based on the previous value:
setCount(prev => prev + 1);
- Or restructure the logic to avoid a
state → effect → statecycle.
Cleanup Functions: What They Are and Why We Need Them
Cleanup functions run before the effect runs again or when the component unmounts.
Syntax
useEffect(() => {
console.log("Effect started");
return () => {
console.log("Cleanup before re‑run or unmount");
};
}, []);
Typical uses
- Removing event listeners
- Unsubscribing from WebSockets, Firebase, etc.
- Clearing intervals and timeouts
- Removing observers
- Aborting fetch requests
Example (listener cleanup)
useEffect(() => {
const handleResize = () => console.log("Resized");
window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", handleResize);
};
}, []);
Without cleanup you get memory leaks.
Real‑World Use Cases of useEffect
1️⃣ Fetching API Data
useEffect(() => {
async function loadData() {
const res = await fetch("https://api.example.com/data");
const data = await res.json();
setItems(data);
}
loadData();
}, []);
2️⃣ Updating Document Title
useEffect(() => {
document.title = `Count: ${count}`;
}, [count]);
3️⃣ Timers
useEffect(() => {
const timer = setInterval(() => {
console.log("Tick");
}, 1000);
return () => clearInterval(timer);
}, []);
4️⃣ Adding Event Listeners
useEffect(() => {
const handler = () => console.log("Clicked");
window.addEventListener("click", handler);
return () => window.removeEventListener("click", handler);
}, []);
5️⃣ Working with localStorage
Save to storage whenever name changes:
useEffect(() => {
localStorage.setItem("name", name);
}, [name]);
TL;DR
useEffecthandles side effects after rendering.- Choose the right dependency strategy (
none,[],[deps]). - Always memoize objects/functions if they’re in the dependency array.
- Use cleanup functions to avoid leaks.
- Prevent infinite loops by not causing state updates that trigger the same effect.
Happy coding! 🎉
Fullscreen Mode
- Enter fullscreen mode
- Exit fullscreen mode
Load once on mount
useEffect(() => {
const saved = localStorage.getItem("name");
if (saved) setName(saved);
}, []);
Summary
| Concept | Meaning |
|---|---|
| Side effects | Actions outside rendering (API calls, timers, listeners) |
| Why useEffect | Keeps render pure; runs effects after the UI update |
| No dependency | Runs on every render |
| Empty array | Runs once on mount |
| Dependency array | Runs when listed values change |
| Cleanup | Unsubscribes, removes listeners, clears timers |
| Use cases | API calls, title updates, listeners, timers, storage |
Final Thoughts
Learning useEffect is a turning point in becoming comfortable with React.
It powers almost all real‑app functionality—from fetching data to syncing your UI with the outside world.
A deeper comprehension of useEffect supports the creation of components that scale gracefully as application complexity grows.
If you’re learning too, feel free to share your thoughts or questions below! 👇
Happy coding!