A React component calls a state setter directly in its body rather than in an event handler or effect. Depending on whose state it is, the app either throws "Too many re-renders. React limits the number of renders to prevent an infinite loop." or logs a warning about updating a component while rendering a different one. Explain what React is doing in each case.
answer
- depends on whose state it is
- own state can be adjusted, mid-render
- the guard is what stops the loop
- the other component already rendered
- derive it and the problem disappears
basics
~20 sUpdating your own state during render makes React re-run that component immediately without committing; unguarded, it loops until React aborts with "Too many re-renders". Updating a different component's state during render is never valid and React warns, because that component already rendered in this pass.
solid answer
~50 sReact does allow a component to update **its own** state while it is rendering: it discards the output, re-runs the function immediately with the new state, and only then continues to the children — so nothing invalid is ever committed. That is safe only if the update is guarded by a condition, because an unguarded call updates, re-runs, updates again, and React stops the runaway with "Too many re-renders. React limits the number of renders to prevent an infinite loop." Calling **another** component's setter during render is a different problem: that component may already have rendered in this pass, so React cannot fold the update into the current work. It warns that a component cannot be updated while rendering a different component, and schedules an extra pass. The fix is almost always to move the call into an event handler or an effect, or to derive the value instead of storing it.
code
javascript · 16 linesimport { useState } from 'react';
function Row({ items, onCount }) {
onCount(items.length); // parent's setter during render -> warning
return <div>{items.length}</div>;
}
function SafeRow({ items }) {
const [prevCount, setPrevCount] = useState(items.length);
const [selected, setSelected] = useState(null);
if (prevCount !== items.length) { // guarded self-update: allowed
setPrevCount(items.length);
setSelected(null);
}
return <div>{selected ?? items.length}</div>;
}go deeper
Recognise the message "Too many re-renders" as a loop you created by updating state in the component body, and know that state updates normally belong in event handlers.
Explain the difference between updating your own state during render, which React folds into the same pass without committing, and updating another component's, which it warns about.
Diagnose it from the stack trace, and rank the fixes: derive the value, move it to a handler, use an effect only for genuine external synchronisation, and treat the guarded self-update as a last resort.
Frame it as an ordering-dependency problem — code that updates other components mid-render makes render results depend on traversal order, which is the property that keeps concurrent rendering safe to interrupt and retry.
## Two different failures that look alike Both start with the same mistake — calling a setter from a component body — but React responds differently depending on whose state it is, and the difference is worth being able to explain. ## Case 1: updating your own state during render This is a supported, if narrow, mechanism. When a component calls its own `useState` setter while rendering, React notices before it commits anything: it throws away the elements that call was about to return, re-runs the component function immediately with the updated state, and only then renders the children. The intermediate output never reaches the DOM, so there is no flash of wrong UI and no extra commit. The catch is that the loop only terminates if the update stops happening. This never terminates: ```jsx function Broken({ value }) { const [seen, setSeen] = useState(0); setSeen(value); // unconditional — runs on every re-run return <span>{seen}</span>; } ``` React re-runs, the line runs again, another update is scheduled, and after a bounded number of attempts React gives up and throws "Too many re-renders. React limits the number of renders to prevent an infinite loop." That error is a guard rail, not a bug: without it the tab would hang. The legitimate form is guarded, so the condition becomes false after one adjustment: ```jsx function List({ items }) { const [prevCount, setPrevCount] = useState(items.length); const [selected, setSelected] = useState(null); if (prevCount !== items.length) { // false on the immediate re-run setPrevCount(items.length); setSelected(null); } return <Rows items={items} selected={selected} />; } ``` Even then, treat it as a last resort. Most of the time the value did not need to be state at all. ## Case 2: updating a different component during render ```jsx function Child({ items, onCount }) { onCount(items.length); // onCount is the parent's setState return <div>{items.length}</div>; } ``` Here the update targets a component React has already finished rendering in this pass. React cannot quietly re-run just that component the way it can for the current one — the parent's output, and the elements it produced for its other children, are already computed. So React logs a development warning that a component cannot be updated while rendering a different component, then schedules the update as separate work, producing an extra render pass after this one. If the child does it on every render, you get a render loop across two components that is much harder to read in a profiler than the single-component version. It is also a correctness smell independent of performance: a component's output now depends on *when* another component happened to render, which is exactly the ordering dependency the pure render phase is designed to eliminate. ## How to diagnose it - The error text names the limit, but not the component. Look at the top frames of the stack trace in the console — the component being rendered when the update fired is right there. - The cross-component warning includes both component names, which usually identifies the offending prop callback immediately. - If neither error appears but the app is slow, look for handlers passed as props being *called* rather than *passed*: `onChange={handler()}` instead of `onChange={handler}` invokes it during render and is the same bug wearing a disguise. ## The fixes, in order of preference 1. **Derive instead of store.** If the value can be computed from props and existing state, compute it in the body and delete the state entirely. No update, no loop. 2. **Move it to an event handler.** If the update should happen because the user did something, that is where it belongs — handlers run after commit and can update anything. 3. **Move it to an effect, with the right dependencies.** Appropriate when React genuinely must synchronise with something outside itself, less appropriate as a way to copy props into state. 4. **Guarded self-update during render.** Only for adjusting a component's own state, only behind a condition that becomes false. The ordering matters in an interview: reaching straight for an effect to silence the warning is the answer that signals you are treating the symptom, because it usually just moves the same loop one commit later.
- When is calling a setter during render actually legitimate?Only for the component's own state, and only behind a condition that becomes false after the adjustment — typically resetting some state when a prop changes. React discards the in-progress output and re-runs the component immediately, so nothing invalid is committed. It is still a last resort; deriving the value is usually better than storing and correcting it.
- Why can't React just apply an update aimed at a parent that is mid-render?Because the parent has already produced its output for this pass, along with elements for its other children. Accepting the update would mean invalidating completed work and making results depend on the order children rendered in. React keeps the render phase order-independent instead, warns, and schedules the update as a separate pass.
- A component is slow and the console shows no errors, but a handler seems to fire on every render. What would you look for?A callback being invoked in JSX rather than passed: `onClick={save()}` calls `save` during render instead of on click. If `save` updates state, you get a render loop; if it sends a request, you get one per render. Passing the function itself, or an arrow wrapping it, fixes it.
saying these in an interview costs you the question
- Wraps the setter in an effect to silence the warning without asking why
- Thinks any setState during render is forbidden by React
- Believes React commits the intermediate render before re-running
- Blames the 'Too many re-renders' guard rail for the bug
- Copies props into state on every render as a matter of course