---
# === IDENTITY ===
id: software/debugging/react-too-many-rerenders/2026
canonical_question: "How do I fix \"Too many re-renders\" in React?"
aliases:
  - "React too many re-renders"
  - "Too many re-renders React"
  - "React infinite loop"
  - "React infinite re-render"
  - "React setState in render"
  - "React maximum update depth exceeded"
  - "React re-render loop"
  - "React limits the number of renders to prevent an infinite loop"
entity_type: software_reference
domain: software > debugging > react_too_many_rerenders
region: global
jurisdiction: global
temporal_scope: 2020-2026

# === VERIFICATION ===
last_verified: 2026-05-17
confidence: 0.93
version: 2.1
first_published: 2026-02-17

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "React Compiler 1.0 (2025-10-07) — auto-memoization reduces manual useMemo/useCallback need"
  next_review: 2026-11-13
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "React hooks (useState, useEffect, useMemo, useCallback) require React 16.8+ — class components use different lifecycle patterns"
  - "Never call setState synchronously in the render phase (component body) — always use event handlers, useEffect, or useLayoutEffect"
  - "React Compiler 1.0 auto-memoizes but does NOT prevent infinite loops caused by setState-during-render or missing dependency arrays — core patterns still apply"
  - "useEffect cleanup functions must be returned for subscriptions, intervals, and event listeners to prevent memory leaks and stale closures"
  - "Rules of Hooks: never call hooks conditionally or inside loops — this breaks React's internal hook ordering"
  - "React StrictMode double-invokes effects in development only — do not suppress StrictMode to 'fix' re-render issues"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Component renders but is slow with no error message (performance issue, not infinite loop)"
    use_instead: "Use React DevTools Profiler — see Kent C. Dodds 'Fix the Slow Render Before You Fix the Re-render'"
  - condition: "White screen or blank page with no console error (React crash boundary)"
    use_instead: "software/debugging/react-white-screen/2026"
  - condition: "Error is 'Objects are not valid as a React child' or hydration mismatch"
    use_instead: "software/debugging/react-hydration-mismatch/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: react_version
    question: "Which React version are you using?"
    type: choice
    options: ["React 16.8-17", "React 18", "React 19+", "Not sure"]
  - key: error_message
    question: "What is the exact error message?"
    type: choice
    options: ["Too many re-renders", "Maximum update depth exceeded", "Component renders infinitely (no error)", "Not sure"]
  - key: using_compiler
    question: "Is the React Compiler enabled in your build?"
    type: choice
    options: ["Yes", "No", "Not sure"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/debugging/react-too-many-rerenders/2026"
suggested_citation: "Source: knowledgelib.io — AI Knowledge Library (verified 2026-05-17)"

# === RELATED UNITS ===
related_kos:
  related_to:
    - id: "software/migrations/jquery-to-react/2026"
      label: "jQuery to React Migration"
    - id: "software/migrations/react-classes-to-hooks/2026"
      label: "React Class Components to Hooks Migration"
  often_confused_with:
    - id: "software/debugging/react-white-screen/2026"
      label: "React White Screen of Death"
    - id: "software/debugging/react-hydration-mismatch/2026"
      label: "React Hydration Mismatch Errors"

# === SOURCES (5-8 authoritative sources) ===
sources:
  - id: src1
    title: "React — useState Hook"
    author: React.dev
    url: https://react.dev/reference/react/useState
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src2
    title: "React — useEffect Hook"
    author: React.dev
    url: https://react.dev/reference/react/useEffect
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src3
    title: "React — useMemo and useCallback"
    author: React.dev
    url: https://react.dev/reference/react/useMemo
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src4
    title: "React — Rules of Hooks"
    author: React.dev
    url: https://react.dev/reference/rules/rules-of-hooks
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src5
    title: "Fix the Slow Render Before You Fix the Re-render"
    author: Kent C. Dodds
    url: https://kentcdodds.com/blog/fix-the-slow-render-before-you-fix-the-re-render
    type: technical_blog
    published: 2025-03-01
    reliability: high
  - id: src6
    title: "React DevTools Profiler"
    author: React.dev
    url: https://react.dev/learn/react-developer-tools
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src7
    title: "React Compiler v1.0 — Automatic Memoization"
    author: React.dev
    url: https://react.dev/blog/2025/10/07/react-compiler-1
    type: official_docs
    published: 2025-10-07
    reliability: authoritative
  - id: src8
    title: "React Compiler Introduction"
    author: React.dev
    url: https://react.dev/learn/react-compiler/introduction
    type: official_docs
    published: 2025-10-07
    reliability: authoritative
---

# How to Fix "Too Many Re-renders" in React

## TL;DR

- **Bottom line**: "Too many re-renders" means your component calls `setState` during render, creating an infinite loop. The 4 most common causes: (1) calling a function instead of passing a reference in `onClick`, (2) `setState` directly in the component body, (3) `useEffect` without proper dependency array, (4) object/array as `useEffect` dependency creating new reference each render.
- **Key tool/command**: React DevTools Profiler (Components tab -> click "Highlight updates") to see which components re-render and why.
- **Watch out for**: `onClick={handleClick()}` (calls immediately) vs `onClick={handleClick}` (passes reference) — the parentheses `()` are the #1 cause.
- **Works with**: React 16.8+ (hooks), React 18+, React 19+ with Compiler. React Compiler 1.0 auto-memoizes but does NOT prevent these infinite loop patterns.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- React hooks require React 16.8+ — class components use `componentDidUpdate` / `shouldComponentUpdate` instead of `useEffect` / `useMemo`. [src1]
- Never call `setState` synchronously in the component body (render phase) — always wrap in `useEffect`, `useLayoutEffect`, or an event handler. [src1, src2]
- React Compiler 1.0 (stable Oct 2025) handles auto-memoization but does NOT fix `setState`-during-render or missing `useEffect` dependency arrays — the core infinite loop patterns in this guide still apply. [src7, src8]
- `useEffect` cleanup must be returned for subscriptions, intervals, and listeners to prevent memory leaks and stale closures. [src2]
- Never call hooks conditionally or inside loops — this breaks React's internal hook ordering (Rules of Hooks). [src4]
- React 19 removed `ReactDOM.render()` — use `createRoot()` from `react-dom/client`. The re-render patterns themselves are unchanged. [src1]

## Quick Reference

| Pattern | Causes Infinite Loop? | Fix |
|---|---|---|
| `onClick={handleClick()}` | YES — calls on every render | `onClick={handleClick}` (remove parentheses) [src1] |
| `onClick={() => handleClick(id)}` | No — arrow function wraps call | Already correct [src1] |
| `setState(value)` in component body | YES — sets state during render | Move into `useEffect` or event handler [src1] |
| `useEffect(() => { setState() })` (no deps) | YES — runs every render | Add dependency array: `useEffect(..., [])` [src2] |
| `useEffect(..., [obj])` where `obj = {}` each render | YES — new ref each render | `useMemo` the object, or destructure primitives [src2, src3] |
| `setState(prev => prev + 1)` in `useEffect(..., [count])` | YES — updates dep, re-runs effect | Remove `count` from deps or use functional update [src2] |
| `useMemo(() => compute(a,b), [a,b])` | No — memoized correctly | Already correct [src3] |
| Conditional `useState` / `useEffect` | YES — breaks Rules of Hooks | Never call hooks conditionally [src4] |
| `useRef` + mutation in render | No (usually) — refs don't trigger re-render | Correct for mutable values that don't affect UI [src1] |
| `flushSync(() => setState())` inside `useEffect` | YES — forces synchronous re-render | Remove `flushSync` or move outside effect [src2] |

## Decision Tree

```
START
├── Error: "Too many re-renders"
│   ├── Check onClick/onChange handlers
│   │   ├── Found: onClick={fn()} → FIX: onClick={fn} or onClick={() => fn()) [src1]
│   │   └── Handlers look correct → Check component body ↓
│   ├── Check for setState in component body (outside useEffect/handlers)
│   │   ├── Found: setState(x) in body → FIX: Move to useEffect or event handler [src1]
│   │   └── No setState in body → Check useEffect ↓
│   ├── Check useEffect dependency arrays
│   │   ├── Missing deps array → FIX: Add [] or [specific deps] [src2]
│   │   ├── Object/array in deps → FIX: useMemo the dep or use primitives [src3]
│   │   └── State var in deps that effect updates → FIX: Remove from deps, use functional update [src2]
│   ├── Check for conditional hooks
│   │   ├── Found → FIX: Move condition inside hook, not around it [src4]
│   │   └── None → Check for flushSync in effects ↓
│   ├── Check for flushSync inside useEffect
│   │   ├── Found → FIX: Remove flushSync or restructure [src2]
│   │   └── None → Use React DevTools Profiler to trace re-renders [src6]
│   └── Using React Compiler?
│       ├── YES → Compiler handles memoization but NOT setState-in-render loops [src7]
│       └── NO → Consider enabling compiler for automatic memoization [src8]
├── Error: "Maximum update depth exceeded"
│   └── Same causes as above — React's bailout kicked in
└── DEFAULT → Add console.log('render', Date.now()) to find the looping component
```

## Decision Logic

### If error message is "Too many re-renders" and you have onClick/onChange handlers
--> Search for `onClick={fn()}` patterns first — accidental invocation is the #1 cause (~40% of cases). Replace with `onClick={fn}` or `onClick={() => fn(args)}`. [src1]

### If error persists after fixing handlers, and you see `setState` calls outside hooks/handlers
--> The setter is being called during render. Move it into `useEffect`, `useLayoutEffect`, or an event handler. Never call setters synchronously in the component body. [src1, src2]

### If error fires on mount with `useEffect` and no dependency array
--> Add a dependency array. Use `[]` for mount-only side effects, `[dep1, dep2]` for re-runs on specific changes. Run eslint with `react-hooks/exhaustive-deps` to catch this automatically. [src2, src4]

### If `useEffect` runs forever and dependency is an object or array literal
--> Object/array references change every render. Wrap in `useMemo`, destructure to primitives, or move the literal outside the component. `[{a: 1}] !== [{a: 1}]` by reference. [src2, src3]

### If the loop is `setState(prev + 1)` inside `useEffect(..., [count])`
--> Either remove `count` from the dependency array and use the functional form `setState(c => c + 1)`, or rethink whether the effect should depend on the same state it updates. [src2]

### If hooks are called conditionally (inside `if`, loops, or after early returns)
--> Move the condition INSIDE the hook callback. Hooks must be called in the same order on every render to preserve React's internal hook index. [src4]

### If you are on React 19 with the Compiler enabled and still hit this error
--> The Compiler auto-memoizes `useMemo`/`useCallback` but cannot fix `setState`-during-render or missing dependency arrays. All rules above still apply. [src7, src8]

### DEFAULT
--> Add `console.log('render', Date.now())` at the top of the suspect component to confirm the loop, then walk the Decision Tree above. Use React DevTools Profiler to see render counts and which props/state changed. [src6]

## Step-by-Step Guide

### 1. Identify the offending component

Add temporary logging to find which component is looping. [src6]

```jsx
function MyComponent() {
  console.log('MyComponent rendered at', Date.now());
  // ... rest of component
}
```

Or use React DevTools -> Settings -> "Highlight updates when components render".

**Verify**: Open DevTools console -> if one component floods the log, that's your culprit.

### 2. Check event handlers for accidental function calls

```jsx
// ❌ WRONG — calls handleClick on EVERY render (infinite loop) [src1]
<button onClick={handleClick()}>Click</button>

// ✅ CORRECT — passes function reference
<button onClick={handleClick}>Click</button>

// ✅ CORRECT — wraps with arrow for arguments
<button onClick={() => handleClick(id)}>Click</button>
```

**Verify**: Search codebase for `onClick={` followed by `()}` — any match with parentheses is suspect.

### 3. Check for setState in the component body

```jsx
// ❌ WRONG — setState during render = infinite loop [src1]
function Counter() {
  const [count, setCount] = useState(0);
  setCount(count + 1);  // BOOM — re-render → setState → re-render → ...
  return <p>{count}</p>;
}

// ✅ CORRECT — setState in event handler or useEffect
function Counter() {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
```

**Verify**: Search for any `set` function calls outside of `useEffect`, `useLayoutEffect`, or event handlers.

### 4. Fix useEffect dependency arrays

```jsx
// ❌ WRONG — no dependency array = runs after EVERY render [src2]
useEffect(() => {
  setData(fetchedData);  // triggers re-render → effect runs again → ...
});

// ✅ CORRECT — empty array = runs once on mount
useEffect(() => {
  fetch('/api/data').then(r => r.json()).then(setData);
}, []);

// ❌ WRONG — object reference changes every render [src2, src3]
const options = { page: 1, limit: 10 };  // NEW object each render
useEffect(() => {
  fetchData(options);
}, [options]);  // options !== prevOptions → re-run → ...

// ✅ CORRECT — memoize the object
const options = useMemo(() => ({ page: 1, limit: 10 }), []);
useEffect(() => {
  fetchData(options);
}, [options]);
```

**Verify**: Run the eslint rule `react-hooks/exhaustive-deps` to catch missing or incorrect dependencies.

### 5. Verify with React DevTools Profiler

Open React DevTools -> Profiler tab -> Record -> interact with the component -> Stop -> analyze the flamegraph for components that render repeatedly without user interaction. [src6]

**Verify**: Components should render 1-2 times on mount (2 in StrictMode), not continuously.

## Code Examples

### React: Common infinite loop patterns and fixes

> Full script: [react-common-infinite-loop-patterns-and-fixes.jsx](scripts/react-common-infinite-loop-patterns-and-fixes.jsx) (55 lines)

```jsx
// Input:  Component with "Too many re-renders" error
// Output: Fixed component that renders correctly
import { useState, useEffect, useMemo, useCallback } from 'react';
// ❌ PATTERN 1: setState during render
function BadComponent1() {
# ... (see full script)
```

### React: useCallback to prevent child re-renders

```jsx
// ❌ BAD — inline function creates new ref each render [src3]
function Parent() {
  const [count, setCount] = useState(0);
  return <Child onClick={() => console.log('click')} />;
  // Child re-renders every time Parent renders (even if memoized)
}

// ✅ GOOD — useCallback stabilizes the function reference [src3]
function Parent() {
  const [count, setCount] = useState(0);
  const handleClick = useCallback(() => console.log('click'), []);
  return <Child onClick={handleClick} />;
  // Child skips re-render if memoized with React.memo
}

// ✅ BEST (React 19+) — React Compiler handles this automatically [src7]
// No manual useCallback needed — compiler inserts memoization at build time
function Parent() {
  const [count, setCount] = useState(0);
  return <Child onClick={() => console.log('click')} />;
  // Compiler auto-memoizes — Child skips re-render automatically
}
```

### React 19+: Compiler does NOT fix setState-in-render

```jsx
// ❌ STILL BROKEN WITH REACT COMPILER — setState during render [src7, src8]
function BrokenEvenWithCompiler() {
  const [count, setCount] = useState(0);
  setCount(count + 1);  // Compiler cannot fix this — infinite loop
  return <p>{count}</p>;
}

// ✅ FIX — same as always: move to effect or handler [src1]
function Fixed() {
  const [count, setCount] = useState(0);
  useEffect(() => { setCount(c => c + 1); }, []);  // Runs once
  return <p>{count}</p>;
}
```

## Anti-Patterns

### Wrong: Calling setState to "derive" state

```jsx
// ❌ BAD — unnecessary state + infinite loop risk [src1]
const [filteredItems, setFilteredItems] = useState([]);
useEffect(() => {
  setFilteredItems(items.filter(i => i.active));
}, [items]);
```

### Correct: Compute derived values during render

```jsx
// ✅ GOOD — no state needed, no effect, no loop risk [src3, src5]
const filteredItems = useMemo(
  () => items.filter(i => i.active),
  [items]
);
```

### Wrong: Conditional hooks

```jsx
// ❌ BAD — violates Rules of Hooks [src4]
if (isLoggedIn) {
  useEffect(() => { fetchProfile(); }, []);
}
```

### Correct: Condition inside the hook

```jsx
// ✅ GOOD — hook always called, condition inside [src4]
useEffect(() => {
  if (isLoggedIn) fetchProfile();
}, [isLoggedIn]);
```

### Wrong: Using flushSync inside useEffect

```jsx
// ❌ BAD — forces synchronous re-render inside effect [src2]
useEffect(() => {
  flushSync(() => {
    setCount(c => c + 1);
  });
}, []);
```

### Correct: Let React batch naturally

```jsx
// ✅ GOOD — React 18+ batches all setState calls automatically [src2]
useEffect(() => {
  setCount(c => c + 1);  // Batched with any other updates
}, []);
```

## Common Pitfalls

- **Parentheses in onClick**: `onClick={fn()}` calls the function during render. This is the #1 cause of "too many re-renders" for beginners. Fix: `onClick={fn}` or `onClick={() => fn(args)}`. [src1]
- **Deriving state with setState + useEffect**: If you can compute a value from existing state/props, don't store it in state. Use `useMemo` instead. [src3, src5]
- **Object/array dependencies in useEffect**: `[{a: 1}] !== [{a: 1}]` — objects are compared by reference. Fix: use `useMemo`, or destructure into primitives. [src2]
- **Functional updates forgotten**: `setCount(count + 1)` in useEffect requires `count` in deps. `setCount(c => c + 1)` doesn't — use functional form to avoid dep loops. [src1, src2]
- **StrictMode double-invoke**: React StrictMode calls effects twice in dev, which can surface hidden re-render issues that seem like infinite loops but aren't. Fix: ensure effects are idempotent. Don't disable StrictMode. [src2]
- **React Compiler false confidence**: Developers on React 19+ with Compiler enabled may assume all re-render issues are auto-fixed. The Compiler handles memoization (useMemo/useCallback) but cannot fix setState-in-render or missing dependency arrays. [src7, src8]
- **useLayoutEffect vs useEffect confusion**: `useLayoutEffect` runs synchronously after DOM mutations but before paint. Calling setState inside without care can cause visible flickering or loops. Use `useEffect` unless you need to measure DOM. [src2]
- **Stale closure in intervals**: `setInterval` inside `useEffect` captures the initial state value. Fix: use functional update `setState(prev => prev + 1)` or a ref. [src1, src2]

## Diagnostic Commands

```bash
# Install React DevTools browser extension
# Chrome: chrome://extensions → search "React Developer Tools"

# Enable "Highlight updates when components render"
# React DevTools → Settings (gear) → Components → check "Highlight updates"

# Profile re-renders
# React DevTools → Profiler tab → Record → interact → Stop → analyze flamegraph

# Check if React Compiler is active (look for "Memo" badges in DevTools)
# React DevTools → Components → look for compiler-inserted cache indicators

# Lint for dependency issues
npx eslint --rule 'react-hooks/exhaustive-deps: warn' src/

# Add React.Profiler to measure specific components
<React.Profiler id="MyComponent" onRender={(id, phase, duration) => {
  console.log(id, phase, duration);
}}>
  <MyComponent />
</React.Profiler>
```

## Version History & Compatibility

| React Version | Re-render Behavior | Notes |
|---|---|---|
| React 16.8+ | Hooks introduced (useState, useEffect) | First version where this guide applies [src1] |
| React 17 | Automatic batching for event handlers only | Multiple setState in handlers = 1 re-render |
| React 18 | Automatic batching everywhere (fewer re-renders) | createRoot required; setState in promises/timeouts now batched |
| React 19 | React Compiler 1.0 (auto-memoization) | Reduces need for manual useMemo/useCallback; core infinite loop patterns unchanged [src7] |

## When to Use / When Not to Use

| This Guide Fixes | Look Elsewhere For | Alternative |
|---|---|---|
| "Too many re-renders" error | Slow rendering (no error) | React profiling guide [src5] |
| "Maximum update depth exceeded" | White screen of death (component crash) | React error boundary guide |
| Component renders endlessly in loop | Component renders but shows wrong data | React state debugging guide |
| useEffect infinite loop | Server-side rendering hydration mismatches | React hydration error guide |

## Important Caveats

- React 19's Compiler (stable Oct 2025) auto-memoizes at build time, eliminating most manual `useMemo`/`useCallback`. But the core infinite loop patterns (don't setState during render, fix onClick calls, fix useEffect deps) still apply because the Compiler optimizes memoization, not control flow. [src7, src8]
- `useEffect` with no dependency array runs after EVERY render — this is almost never what you want. Always specify dependencies. [src2]
- React's "Too many re-renders" error is a safety bailout (typically after ~50 renders). The real issue is the infinite loop pattern, not the error count. [src1]
- React 18+ automatic batching means multiple `setState` calls in the same synchronous block produce only one re-render. This can mask bugs that appear in React 17 but not 18+. [src1]

## Related Units
<!-- Generated from related_kos frontmatter -->

- [jQuery to React Migration](/software/migrations/jquery-to-react/2026)
- [React Class Components to Hooks Migration](/software/migrations/react-classes-to-hooks/2026)
- [React White Screen of Death](/software/debugging/react-white-screen/2026)
- [React Hydration Mismatch Errors](/software/debugging/react-hydration-mismatch/2026)
