How to Resolve useEffect Infinite Loop Caused by Dependency Array Issues
I ran into a frustrating issue where my React component kept making API calls endlessly. The page would load, freeze, and eventually crash. When I checked the network tab, I saw thousands of requests to the same endpoint. This is a classic useEffect infinite loop problem.
The Problem
Here’s the code that caused my infinite loop:
function UserList() { const [users, setUsers] = useState([]); const [page, setPage] = useState(1);
useEffect(() => { fetchUsers().then(setUsers); }, [{ page }]); // This object is recreated every render!
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;}Every time the component rendered, React created a new { page } object. Since this object had a new reference each time, React thought the dependency changed and ran the effect again. This triggered a re-render, which created another new object, and the cycle continued forever.
Why This Happens
useEffect runs after every render where its dependencies change. React compares dependencies using Object.is, which checks reference equality for objects and functions.
When you include:
- Objects like
{ page: 1 }- new reference every render - Functions like
() => {}- new reference every render - Arrays like
[1, 2, 3]- new reference every render
React sees them as different values each time because their references change, even if their contents don’t.
The Solution
I fixed this by using useMemo to memoize the object:
function UserList() { const [users, setUsers] = useState([]); const [page, setPage] = useState(1);
const params = useMemo(() => ({ page }), [page]);
useEffect(() => { fetchUsers(params).then(setUsers); }, [params]); // params reference stays stable
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;}Or I could just use the primitive value directly:
function UserList() { const [users, setUsers] = useState([]); const [page, setPage] = useState(1);
useEffect(() => { fetchUsers({ page }).then(setUsers); }, [page]); // primitive value - compared by value, not reference
return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;}Handling Functions in Dependencies
When I needed to pass a function as a dependency, I used useCallback:
function UserProfile({ userId }) { const [user, setUser] = useState(null);
const fetchUser = useCallback(async () => { const response = await fetch(`/api/users/${userId}`); return response.json(); }, [userId]); // only recreated when userId changes
useEffect(() => { fetchUser().then(setUser); }, [fetchUser]); // stable reference
return <div>{user?.name}</div>;}Common Mistakes to Avoid
1. Empty Dependency Array with Dependencies
function BadExample({ userId }) { const [data, setData] = useState(null);
useEffect(() => { fetchData(userId).then(setData); }, []); // Missing userId - stale closure!
return <div>{data}</div>;}This works initially but never updates when userId changes.
2. Inline Functions Without Memoization
function BadExample() { const [count, setCount] = useState(0);
useEffect(() => { const handler = () => console.log(count); window.addEventListener('resize', handler); return () => window.removeEventListener('resize', handler); }, [() => console.log(count)]); // New function every render!
return <div>{count}</div>;}3. Forgetting Cleanup
function BadExample() { useEffect(() => { const interval = setInterval(() => { console.log('polling...'); }, 1000); // Missing cleanup - interval runs forever! }, []);
return <div>Component</div>;}The fix:
function GoodExample() { useEffect(() => { const interval = setInterval(() => { console.log('polling...'); }, 1000); return () => clearInterval(interval); // Clean up! }, []);
return <div>Component</div>;}Using ESLint to Catch Issues
I added the react-hooks/exhaustive-deps rule to my ESLint config:
module.exports = { rules: { 'react-hooks/exhaustive-deps': 'error' }};This rule warns me when I forget to include dependencies or when I include values that might cause issues.
The Real Cost of Infinite Loops
Infinite loops don’t just crash the browser. They can:
- Flood APIs with thousands of requests per second
- Rack up expensive cloud bills
- Cause memory leaks from uncleaned subscriptions
- Crash the user’s browser tab
A recent Reddit discussion highlighted how AI-generated code frequently includes incorrect dependency arrays, leading to “DDOS your own servers 101” scenarios. The key is understanding why dependencies cause loops, not just copying code.
Quick Reference
| Dependency Type | Stable? | Fix |
|---|---|---|
| Primitive (string, number, boolean) | Yes | Use directly |
| setState function | Yes | Don’t include in deps |
| Object literal | No | Use useMemo or extract |
| Array literal | No | Use useMemo or extract |
| Inline function | No | Use useCallback |
| useRef.current | Yes | Use directly |
Final Words + More Resources
My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me
Here are also the most important links from this article along with some further resources that will help you in this scope:
Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!
Comments