Mastering useEffect: Best Practices for Clean and Predictable React Components
React’s useEffect hook is one of the most commonly used — and most misused — tools in the React ecosystem. While it unlocks powerful lifecycle behavior in function components, using it incorrectly can lead to bugs, performance issues, and hard-to-maintain code.
In this post, we’ll go over the most important best practices to follow when working with useEffect, including practical examples and real-world advice.
📌 1. Only Use useEffect When You Need a Side Effect
The name says it all — useEffect is for side effects. These are things that happen outside the normal flow of rendering, like:
- Fetching data
- Subscribing to an event
- Updating the DOM
- Setting up a timer or interval
If you're using useEffect to update state based on props or something already in render scope, you might not need it at all.
❌ Example of Misuse:
useEffect(() => {
setUser(props.user);
}, [props.user]);
Instead, just use props.user directly inside render — there's no side effect here.
🧼 2. Always Declare Your Dependencies Correctly
This is probably the most common mistake with useEffect.
React uses the dependency array to decide when the effect should re-run. Leaving out dependencies can cause stale values or inconsistent behavior.
✅ Correct:
useEffect(() => {
fetchData(userId);
}, [userId]);
❌ Incorrect:
useEffect(() => {
fetchData(userId);
}, []);
If you're tempted to leave the array empty just to avoid re-renders — pause. You might be introducing bugs.
🔄 3. Clean Up After Yourself
When your effect creates something like a subscription or a timer, it should clean it up when the component unmounts or the dependencies change.
useEffect(() => {
const id = setInterval(() => {
console.log('tick');
}, 1000);
return () => clearInterval(id);
}, []);
This prevents memory leaks and unexpected behavior.
🪝 4. Avoid Nesting useEffect Inside Conditionals
useEffect should always be called unconditionally and in the top-level body of your component. Don’t wrap it in if statements or inside functions.
❌ Bad:
if (user) {
useEffect(() => {
fetchData(user.id);
}, [user]);
}
✅ Good:
useEffect(() => {
if (user) {
fetchData(user.id);
}
}, [user]);
React relies on hooks always being called in the same order — conditionally calling them breaks this rule.
🧠 5. Don’t Abuse useEffect for Data Transformation
If you just need to compute a value based on props or state, use useMemo or just do it inline. useEffect should be for things with external effects — like network requests, subscriptions, etc.
❌ Bad:
useEffect(() => {
const updated = items.filter(i => i.active);
setFilteredItems(updated);
}, [items]);
✅ Better:
const filteredItems = useMemo(() => {
return items.filter(i => i.active);
}, [items]);
Or just calculate it directly in render if it's lightweight.
🔁 6. Understand the Difference Between Mount and Update
If you only want an effect to run once on mount (like componentDidMount), use an empty dependency array:
useEffect(() => {
console.log('runs once on mount');
}, []);
But if you're watching a value like userId, your effect will run on mount and whenever userId changes.
If you need more fine-grained control (e.g., you want to skip the first run), you might need a flag:
const didMount = useRef(false);
useEffect(() => {
if (didMount.current) {
console.log('runs on updates only');
} else {
didMount.current = true;
}
}, [someValue]);
✅ Summary: Best Practices Checklist
- ✅ Use useEffect for side effects, not pure logic
- ✅ Declare all dependencies correctly
- ✅ Clean up any subscriptions or timers
- ✅ Never call useEffect conditionally
- ✅ Prefer useMemo or inline code for derived data
- ✅ Know when you're running on mount vs. update
✍️ Final Thoughts
useEffect is essential for managing side effects in modern React apps, but it requires discipline to use well. By following these best practices, you’ll avoid a ton of bugs and keep your components predictable and easy to debug.
Got any other useEffect tips or anti-patterns? Drop them in the comments — let’s make React code cleaner, together.
Member discussion