When I first started working with React Hooks, useEffect
felt like this magical black box. You toss in some logic, slap on a dependency array, and just hope everything works.
Fast forward 3.5 years, and I’ve learned that truly mastering useEffect
can make or break how clean, performant, and bug-free your components are.
In this post, I’ll walk you through what useEffect
actually does, how to use it right, common pitfalls, and some patterns I personally use (and avoid) in production apps.
🧠 What is useEffect
?
At its core, useEffect
is how React lets you synchronize a component with external systems — network requests, subscriptions, DOM mutations, timers, and more.
Think of it as:
“Run this code after the render.”
The basic signature:
useEffect(() => {
// side effect logic here
return () => {
// cleanup logic here (optional)
};
}, [dependencies]);
The useEffect
hook runs after the DOM has been painted. That’s important because unlike class components where componentDidMount
and componentDidUpdate
are separate, useEffect
handles both — and more — depending on how you configure the dependency array.
🔄 Dependency Array Demystified
The second argument is where the real magic happens:
Dependency Array | What Happens |
---|---|
[] |
Runs only once (after initial render) |
[a, b] |
Runs on initial render + whenever a or b change |
(no array) | Runs on every render |
// Example: Fetch on mount
useEffect(() => {
fetchUser();
}, []);
☝️ Always be intentional with dependencies — it affects when your effect runs.
✅ Common Use Cases
Here are a few practical examples I use regularly:
1. 📡 Fetching data on mount
useEffect(() => {
async function fetchData() {
const res = await fetch('/api/data');
const json = await res.json();
setData(json);
}
fetchData();
}, []);
2. 🖱️ Subscribing to events
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth);
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize);
}, []);
3. 🧮 Re-running logic based on props/state
useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
⚠️ Common Pitfalls (I’ve Been There)
❌ Missing dependencies
React doesn’t “guess” what your effect depends on. If you use a variable in your effect but don’t include it in the dependency array, you’ll likely run into bugs — especially async or delayed ones.
✅ Enable ESLint’s react-hooks/exhaustive-deps
rule
It’s a life-saver. Seriously.
❌ Doing too much in one useEffect
// This is not ideal
useEffect(() => {
fetchData();
setupSubscription();
updateLocalStorage();
}, []);
This is a code smell. Each effect should have a single responsibility. Splitting them into multiple useEffect
hooks keeps dependencies accurate and effects easier to debug.
💡 Best Practices
- 🔹 Keep effects focused: One responsibility per hook
- 🧹 Always clean up: Especially for timers, listeners, subscriptions
- 🧠 Use stable references: For objects/functions using
useRef
oruseCallback
- 📏 Don’t define unnecessary functions inside
useEffect
unless they’re only needed there - 🧰 Use custom hooks to extract and reuse common effects cleanly
⚡ Real-World Tip: useEffect
≠ Lifecycle Methods
A mistake I made early on was trying to map lifecycle methods 1:1 with hooks — thinking useEffect
== componentDidMount
or componentDidUpdate
.
But that’s not how hooks work.
Instead, ask yourself:
“What side effect should run in reaction to this state or prop?”
This mental model leads to cleaner, more predictable code than trying to mimic class lifecycles.
🧪 Bonus: When Not to Use useEffect
Sometimes you don’t need useEffect
at all. Some common cases:
- ✅ Derived state: Compute values directly in render or via
useMemo
- ✅ DOM reads/writes before paint: Use
useLayoutEffect
instead - ✅ Transforming props/state: Often better handled directly in JSX or helpers
// ❌ Don't do this
useEffect(() => {
setX(props.value * 2);
}, [props.value]);
// ✅ Do this instead
const x = props.value * 2;
✨ Final Thoughts
useEffect
is powerful — but only when used with care.
Once you stop thinking of it as a lifecycle substitute and start thinking of it as a reaction engine — something that runs when certain values change — things start to click.
So next time you reach for useEffect
, pause and ask:
- 🔍 Am I tracking the correct dependencies?
- ✂️ Can I break this into smaller effects?
- ❓ Do I even need an effect?
Thanks for reading! 🙌
If you found this helpful or have your own useEffect
war stories, I’d love to hear them. Drop a comment or share how you approach effects in your React apps!