Setting state inside render() leading to infinite loop

Loading

Avoiding Infinite Loops from State Updates in Render

A critical React mistake is calling state update functions (like setState or state setters from useState) inside the render() method or function component body, which creates an infinite rendering loop.

Why It’s a Problem

When you update state during render:

  1. React re-renders the component
  2. The render executes again
  3. The state update triggers again
  4. This cycle continues indefinitely

The Anti-Pattern

Class Component Example:

class InfiniteLoop extends React.Component {
  state = { count: 0 };

  render() {
    this.setState({ count: this.state.count + 1 }); // ❌ Infinite loop
    return <div>{this.state.count}</div>;
  }
}

Function Component Example:

function InfiniteLoop() {
  const [count, setCount] = useState(0);
  setCount(count + 1); // ❌ Infinite loop
  return <div>{count}</div>;
}

Correct Solutions

1. Move State Updates to Event Handlers

function Counter() {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(c => c + 1);
  };

  return (
    <div>
      <span>{count}</span>
      <button onClick={increment}>+</button>
    </div>
  );
}

2. Use useEffect for Side Effects

function DataFetcher() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetchData().then(setData); // ✅ Safe side effect
  }, []);

  return data ? <Display data={data} /> : <Loading />;
}

3. Initialize State Properly

// Instead of setting derived state in render:
function Component({ items }) {
  const [filteredItems, setFilteredItems] = useState([]);

  // ❌ Bad - infinite loop risk
  setFilteredItems(items.filter(i => i.active));

  // ✅ Better - use memo or direct initialization
  const filteredItems = useMemo(() => 
    items.filter(i => i.active), 

[items]

); }

Common Scenarios

1. Conditional State Updates

Wrong:

function UserProfile({ user }) {
  const [profile, setProfile] = useState(null);

  if (user && !profile) {
    setProfile(user.profile); // ❌ Conditional state update in render
  }
}

Right:

function UserProfile({ user }) {
  const [profile, setProfile] = useState(null);

  useEffect(() => {
    if (user) {
      setProfile(user.profile); // ✅ Safe in useEffect
    }
  }, [user]);
}

2. Derived State

Wrong:

function List({ items }) {
  const [filtered, setFiltered] = useState([]);
  setFiltered(items.filter(i => i.active)); // ❌
}

Right:

function List({ items }) {
  const filtered = items.filter(i => i.active); // ✅ Just calculate directly
}

Debugging Infinite Loops

  1. Check for setState calls in:
  • render() method
  • Function component body
  • Custom hooks that might run during render
  1. Look for:
  • Browser freezing
  • Maximum update depth warnings
  • Infinite network requests
  1. Use React DevTools profiler to detect excessive renders

Performance Impact

Each accidental state update during render:

  1. Triggers reconciliation process
  2. Causes DOM updates
  3. May trigger child component updates
  4. Wastes CPU cycles and battery life

Key Rules

  1. Never call state setters during render
  2. Move side effects to useEffect or event handlers
  3. For derived values, use:
  • Direct calculation
  • useMemo
  • useReducer for complex state logic

Leave a Reply

Your email address will not be published. Required fields are marked *