A React component's status state is the string 'idle', and a click handler calls setStatus('idle') again with the identical value. Does React re-render the component, and what comparison decides that?
answer
- React checks before it renders
- identity, not deep equality
- Object.is decides the bail-out
- subtree skipped, this component maybe not
basics
~20 sReact compares the next state with the current one using Object.is. Because the values match, it bails out and skips re-rendering the subtree — though it may still run this component's function once before bailing out, so never rely on that render not happening.
solid answer
~50 sSetting state to a value that matches the current one does not force a re-render. When React processes the update it compares the resulting state with the current state using `Object.is`, and on a match it bails out: the children are not re-rendered and no effects fire for the change. Two caveats matter in practice. First, React may still call *this* component's function once before it notices there is nothing to do, so an unconditional `console.log` in the body can still print — the guarantee is about not going deeper into the tree, not about your component never running. Second, the comparison is by identity, not by contents: `setUser({ ...user })` with all the same fields is a different object, so `Object.is` reports a difference and the component and its children re-render. That asymmetry between primitives and freshly-allocated objects is the source of most "why does this re-render" confusion around this bail-out.
go deeper
Know that setting state to the value it already has does not update the screen, and that React decides this by comparing the new value with the old one rather than by re-rendering anyway.
Name Object.is as the comparison and state precisely what the bail-out skips — the subtree and the resulting DOM and effect work — while your own component may still run once.
Use the asymmetry when debugging: a redundant primitive set is silently free, while a rebuilt equal object always re-renders, and a mutated-then-reset same reference produces a stale screen. Prescribe fixes at the call site, not workarounds around the comparison.
Set the expectation that this bail-out is a cheap safety net, never a render-control strategy. Push teams toward state shapes where redundant sets are naturally identical values, and toward deriving during render instead of storing rebuilt objects.
## Where the comparison sits The bail-out is part of the update pipeline, not a separate optimisation you enable. When a setter is called, React ends up needing the next value of that state — either computed straight away when nothing is pending, or produced by folding the update queue on the next render. Either way, before doing work it compares the resulting state with the state currently in place using `Object.is`. Same value → bail out; different value → proceed with rendering. `Object.is` is the JavaScript sameness comparison React standardises on for this. What matters for React is only that it is an *identity/primitive-value* comparison, not a structural one: two distinct objects with identical fields are never "the same" by this test. ## What "bails out" actually skips On a match, React skips re-rendering the component's children and skips the work that would follow — no new elements for the subtree, no DOM updates from this change, no effects triggered by it. The caveat the documentation is explicit about: **React may still need to render that specific component once before bailing out.** The bail-out stops the work from propagating deeper into the tree; it does not promise your component function is never called. So this is a correctness-neutral optimisation you can rely on for the subtree, and a poor thing to rely on for "my component body definitely will not run". ## The primitive/object asymmetry ```javascript setStatus('idle'); // status is already 'idle' -> matches, bail-out setCount(count); // same number -> matches, bail-out setUser({ ...user }); // brand new object -> different identity, re-render setItems(items.slice()); // copy with equal contents -> different identity, re-render ``` This catches people in two opposite directions. Some assume React does a deep comparison and are surprised that a rebuilt-but-equal object re-renders everything. Others assume every setter call always re-renders and are surprised that a redundant primitive set is free. Both are cured by the same sentence: the test is `Object.is` on the resulting state, nothing deeper. A practical consequence: for a state whose value is an object, you cannot get the bail-out by rebuilding an equal object, so if a handler may compute an unchanged result, compare before setting and skip the call — or derive the value during render instead of storing it at all. ## What it is not This is not a substitute for memoisation, and not a mechanism for stopping re-renders caused by a parent or by context — it only concerns *this* state slot's update. A parent re-rendering still renders this component regardless of what its own state did. It is also not a licence to fire redundant setters in a loop. Each call still allocates an update entry and schedules work; React discovers the equality after the fact. The bail-out protects the subtree from pointless rendering, not your handler from pointless calls. ## Diagnosing with it When someone reports "setting state does nothing", the bail-out is the first hypothesis, and it splits into two very different causes. Either the value genuinely matches — a redundant set, and the UI is already correct — or the *same object reference* was handed back after being modified in place, in which case `Object.is` matches even though the contents changed and the screen is now stale. The second is a real bug and the fix is to produce a new object rather than to fight the comparison. A compact interview answer: React compares the next state with the current one using `Object.is`; equal means bail out and skip the subtree, though this component may still run once; the comparison is identity-based, so equal-looking new objects always re-render.
- You rebuild state as setUser({ ...user }) with every field identical. Does the bail-out kick in?No. The spread allocates a new object, so `Object.is` sees a different value and React renders the component and its children. There is no structural comparison to fall back on. If you want to avoid the render, check whether anything actually changed and skip the setter call.
- Can you rely on your component function not running when React bails out?No. React may still call that component once before it notices the state is unchanged; what the bail-out reliably skips is the work below it in the tree and the DOM and effect work for that change. Treat it as a subtree optimisation, and never put logic in the body that assumes it did not run.
- A colleague suggests calling setState with the same value as a cheap way to stop re-renders. What do you say?That it prevents nothing that was going to happen anyway — the bail-out only applies when the value truly matches, which means the update was redundant to begin with. Re-renders from a parent, from context, or from other state are untouched by it, and each redundant call still allocates an update and schedules work.
saying these in an interview costs you the question
- Says React deep-compares state before re-rendering
- Thinks every setter call always causes a re-render
- Believes a spread copy with equal fields bails out
- Claims the bail-out guarantees the component body never runs
- Uses redundant setState calls as a performance technique