It starts with the browser tab slowing down, and then the fans on your laptop start spinning loudly. Finally, the console screen fills with red warnings:
`Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.`
This is the infamous useEffect infinite loop. It occurs when a state update inside the useEffect hook triggers a re-render, which then runs the useEffect again, updates the state again, and repeats infinitely.
TL;DR - Quick Troubleshooting checklist
- 1Are you updating a state variable inside useEffect without a dependency array?
- 2Are you passing an object or array in the dependency array without memoization?
- 3Are you triggering a function prop that changes state in the parent component?
Root Causes
Missing Dependency Array
Running useEffect without passing a second argument means the hook runs after every single render. If you set state inside it, you create a render-state-render loop.
useEffect(() => { setData(fetchedData); }); // No [] array!Creating New Object/Array References in Dependencies
React uses shallow comparison (Object.is) for dependency arrays. If you include an object or array dependency that is recreated on every render, React thinks it changed and triggers the hook again.
const options = { id: 1 };
useEffect(() => {}, [options]); // options is a new reference every renderUpdating Parent State in a Prop Callback
If your hook runs a function passed from a parent component that updates the parent's state, it triggers a re-render of the parent, which triggers a re-render of the child, triggering the hook again.
Step-by-Step Fix Guide
Add a Proper Dependency Array
Ensure your hook only runs when specific variables change by passing the dependency array as the second argument.
useEffect(() => {
fetchData().then(res => setData(res));
}, []); // Empty array runs hook ONLY once on mountMemoize Object or Array Dependencies
If you must depend on an object or array, wrap it in useMemo or useCallback to preserve reference identity across renders.
const options = useMemo(() => ({ id: userId }), [userId]);
useEffect(() => {
fetchData(options);
}, [options]);Use Functional State Updates
If your state update depends on the previous state value, use a functional update instead of adding the state itself to the dependency array.
// BAD: causes loop if count is a dependency
useEffect(() => {
const interval = setInterval(() => setCount(count + 1), 1000);
return () => clearInterval(interval);
}, [count]);
// GOOD: state is not a dependency anymore
useEffect(() => {
const interval = setInterval(() => setCount(prev => prev + 1), 1000);
return () => clearInterval(interval);
}, []);Stuck in a React Re-render Loop?
React hooks can be tricky to optimize, especially under tight deadlines. Let me step in, profile your component renders, and fix your hooks.
Get Expert React HelpRelated Errors
Error: Maximum update depth exceeded.
This occurs when class components or useEffect loops trigger state changes repeatedly. Isolate state transitions and check all dependency updates.
Prevention Strategy
- Always define a dependency array for every useEffect hook.
- Avoid using complex objects or functions directly in dependency arrays without wrapping them in useMemo/useCallback.
- Use ESLint rules like eslint-plugin-react-hooks (react-hooks/exhaustive-deps) to flag missing dependencies.
Still Stuck With This Issue?
Send your exact error message or deployment issue. I'll respond with a targeted fix.
Need a Deeper Fix?
Describe your full project issue below and I'll get back to you with a targeted fix.