![]()
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:
- React re-renders the component
- The render executes again
- The state update triggers again
- 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
- Check for
setStatecalls in:
render()method- Function component body
- Custom hooks that might run during render
- Look for:
- Browser freezing
- Maximum update depth warnings
- Infinite network requests
- Use React DevTools profiler to detect excessive renders
Performance Impact
Each accidental state update during render:
- Triggers reconciliation process
- Causes DOM updates
- May trigger child component updates
- Wastes CPU cycles and battery life
Key Rules
- Never call state setters during render
- Move side effects to
useEffector event handlers - For derived values, use:
- Direct calculation
useMemouseReducerfor complex state logic
