Skip to content

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:

UserList.js
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:

UserList.js
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:

UserList.js
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:

UserProfile.js
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

BadExample.js
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

BadExample.js
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

BadExample.js
function BadExample() {
useEffect(() => {
const interval = setInterval(() => {
console.log('polling...');
}, 1000);
// Missing cleanup - interval runs forever!
}, []);
return <div>Component</div>;
}

The fix:

GoodExample.js
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:

.eslintrc.js
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 TypeStable?Fix
Primitive (string, number, boolean)YesUse directly
setState functionYesDon’t include in deps
Object literalNoUse useMemo or extract
Array literalNoUse useMemo or extract
Inline functionNoUse useCallback
useRef.currentYesUse 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