React useEffect is a React Hook that lets you synchronize a component with an external system — API calls, subscriptions, DOM events, or timers. It replaces the lifecycle methods componentDidMount, componentDidUpdate, and componentWillUnmount from class components, but its behavior surprises developers daily.
Last updated: June 2026
The Most Common React useEffect Mistakes¶
Mistake 1: Missing Dependency Arrays¶
Any value from the component scope (props, state, functions) used inside useEffect must be in the dependency array or the effect captures a stale reference.
Why it breaks: The effect closure captures count at render time. Without count in the dependency array, the closure never updates, causing unpredictable behavior and stale UI state.
Fix: Include all reactive values in the dependency array.
Mistake 2: Infinite Re-render Loops¶
Updating state inside useEffect without proper guards creates an infinite loop: effect runs, state updates, component re-renders, effect runs again.
Why it breaks: State updates trigger re-renders, which re-run the effect, which updates state again.
Fix: Use functional updates or move the logic outside useEffect.
Mistake 3: No Cleanup for Side Effects¶
Timers, subscriptions, and AbortControllers left behind after unmount cause memory leaks and update-state-on-unmounted-component warnings.
Why it breaks: The interval continues firing even after the component is removed from the DOM, attempting to update state on an unmounted component and leaking memory.
Fix: Return a cleanup function.
Async Functions in useEffect¶
React useEffect does not support async functions directly because async functions return a Promise, not a cleanup function or undefined.
Fix: Define the async function inside the effect and call it.
The AbortController pattern prevents state updates after unmount — a critical practice for production React apps.
Object and Function Dependencies¶
Objects and functions create new references on every render, causing effects to re-run on every render cycle.
Fix: Memoize with useMemo / useCallback or restructure your data to use primitive values.
One Effect per Concern¶
Multiple unrelated side effects in a single useEffect make the dependency array harder to reason about and the cleanup function more complex.
Fix: Split into separate useEffect calls.
Build Better Apps With Reinvoice¶
Running a SaaS? Reinvoice handles invoicing, subscription management, and billing automations so you can focus on code. Start your free trial .
Frequently Asked Questions¶
Is useEffect called after every render?
Yes, by default useEffect runs after every completed render. The dependency array controls when it re-runs. An empty array [] means run once after mount.
Can useEffect be async?
Not directly. Define an async function inside the effect and call it, or use an IIFE pattern.
How do I fix the missing dependency warning?
Add the missing value to the dependency array. If you intentionally want to run the effect only on mount, verify the value is truly stable or use the eslint-disable comment with a justification.
Why does useEffect run twice in development?
React 18 Strict Mode intentionally double-invokes effects in development to surface missing cleanup functions. It does not happen in production builds.
When should I use cleanup in useEffect?
Always clean up subscriptions, timers, AbortControllers, and event listeners. Effects that only synchronize props to local state typically do not need cleanup.

