React throws "Too many re-renders" when it detects an infinite render loop, typically caused by state updates in the component body or improperly configured useEffect hooks. This error is React's safety mechanism to prevent your application from crashing.
This error occurs when React detects that a component is stuck in an infinite rendering cycle. React has a built-in safety mechanism that limits the number of renders to prevent browser crashes and performance degradation. When a component triggers a state update that immediately causes another render, which triggers another state update, React will throw this error after reaching its render limit. The error is most commonly triggered by state updates happening in the wrong place - either directly in the component body, in event handlers that are immediately invoked rather than passed as callbacks, or in useEffect hooks with missing or incorrect dependency arrays. React's rendering model expects that each render is a "snapshot" of the component state, and any state changes should be scheduled for the next render cycle, not executed during the current one.
Check the error stack trace to find which component is triggering the error. React will typically show the component name in the error message. You can also use React DevTools Profiler to see which component is rendering repeatedly.
// The error will show something like:
// Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
// at MyComponent (MyComponent.jsx:15)Once identified, examine all state updates in that component.
Event handlers should receive a function reference, not the result of calling a function. This is one of the most common causes of this error.
// ❌ Wrong - immediately invokes setCount, causing infinite loop
<button onClick={setCount(count + 1)}>Increment</button>
// ✅ Correct - passes a function reference
<button onClick={() => setCount(count + 1)}>Increment</button>
// ✅ Also correct - passes function reference directly
const handleClick = () => setCount(count + 1);
<button onClick={handleClick}>Increment</button>Never call setState directly in the component body. State updates must happen in event handlers, effects, or callbacks.
// ❌ Wrong - setState in component body causes infinite loop
function MyComponent() {
const [count, setCount] = useState(0);
setCount(count + 1); // Runs on every render!
return <div>{count}</div>;
}
// ✅ Correct - setState in useEffect with empty dependency array
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1); // Runs only once on mount
}, []);
return <div>{count}</div>;
}
// ✅ Best - initialize state correctly
function MyComponent() {
const [count, setCount] = useState(1); // Set initial value
return <div>{count}</div>;
}useEffect without a dependency array runs after every render. If it updates state, it triggers another render, creating an infinite loop.
// ❌ Wrong - no dependency array, runs on every render
useEffect(() => {
setData(fetchData());
});
// ✅ Correct - empty array means run once on mount
useEffect(() => {
setData(fetchData());
}, []);
// ✅ Correct - runs when specific dependencies change
useEffect(() => {
setData(fetchData(userId));
}, [userId]);Objects and arrays are compared by reference in JavaScript. Creating new instances on each render causes useEffect to run infinitely.
// ❌ Wrong - new object created on every render
function MyComponent() {
const options = { limit: 10, sort: 'asc' };
useEffect(() => {
fetchData(options);
}, [options]); // Creates infinite loop!
}
// ✅ Correct - use useMemo to preserve reference
function MyComponent() {
const options = useMemo(() => ({
limit: 10,
sort: 'asc'
}), []); // Only created once
useEffect(() => {
fetchData(options);
}, [options]);
}
// ✅ Also correct - move object inside useEffect
function MyComponent() {
useEffect(() => {
const options = { limit: 10, sort: 'asc' };
fetchData(options);
}, []); // options not in dependencies
}If you update state inside useEffect and that state is a dependency, you create an infinite loop. Use the functional update form of setState instead.
// ❌ Wrong - count is both updated and a dependency
useEffect(() => {
setCount(count + 1);
}, [count]); // Infinite loop!
// ✅ Correct - use functional update, remove from dependencies
useEffect(() => {
setCount(prev => prev + 1);
}, []); // Runs once
// ✅ Correct - use a different trigger for the effect
useEffect(() => {
if (shouldUpdate) {
setCount(prev => prev + 1);
}
}, [shouldUpdate]); // Only runs when shouldUpdate changesFunctions created in the component body get new references on each render. Wrap them with useCallback to maintain stable references.
// ❌ Wrong - handleFetch recreated on every render
function MyComponent() {
const [data, setData] = useState(null);
const handleFetch = () => {
fetchData().then(setData);
};
useEffect(() => {
handleFetch();
}, [handleFetch]); // Infinite loop!
}
// ✅ Correct - useCallback maintains stable reference
function MyComponent() {
const [data, setData] = useState(null);
const handleFetch = useCallback(() => {
fetchData().then(setData);
}, []); // Stable reference
useEffect(() => {
handleFetch();
}, [handleFetch]); // Only runs when handleFetch changes (never)
}For debugging complex infinite loop issues, use React DevTools Profiler to record a session and examine the render timeline. You can see exactly which components are rendering and how many times. The "Why did this render?" feature in React DevTools can help identify which props or state changes triggered a render.
In class components, infinite loops often occur in componentDidUpdate when calling setState without proper conditions. Always check if the state or props actually changed before calling setState in lifecycle methods.
When working with custom hooks that return objects or arrays, consider using useMemo inside the hook to maintain stable references. This prevents consumers of your hook from experiencing infinite loops when using the returned values as dependencies.
In Next.js applications, be careful with state updates in getServerSideProps or during server-side rendering. While these won't cause the "too many re-renders" error (since they run on the server), they can cause similar infinite loop issues during hydration.
For complex state management, consider using useReducer instead of multiple useState calls. This can help prevent infinite loops by centralizing state logic and making it easier to reason about state transitions.
Prop spreading could cause security issues
Prop spreading could cause security issues
Error: error:0308010C:digital envelope routines::unsupported
Error: error:0308010C:digital envelope routines::unsupported
React Hook "useEffect" is called conditionally. React Hooks must be called in the exact same order in every component render.
React Hook useEffect placed inside a condition
Hook can only be called inside the body of a function component
Hook can only be called inside the body of a function component
Rollup failed to resolve import during build
How to fix "Rollup failed to resolve import" in React