skip to content

React requires that rendering a component be pure. What does purity mean for a component body concretely, which operations violate it, and what breaks if you ignore the rule?

level: middleimportance: must knowfreq 66%

answer

  1. a calculation, not an errand
  2. same inputs, same elements
  3. mutate only what this render created
  4. the render may be run twice or thrown away
  5. doubled logs in development are the tell

basics

~20 s

A pure component body computes its output from props, state and context alone, changing nothing that existed before it ran. Mutating props or state, writing to shared variables, reading or writing the DOM, and starting requests during render all violate it.

solid answer

~50 s

Purity means the component body behaves like a calculation: given the same props, state and context it returns the same elements, and running it leaves the rest of the world untouched. In practice that rules out mutating anything you did not create in this render — props, an object already in state, a module-level variable, a cache — as well as DOM reads and writes, network calls, subscriptions and imperative logging. Anything with an observable effect belongs in an event handler or an effect instead. The reason is that React treats a render as speculative: it may run your function twice in development under `<StrictMode>` (React 18 and later), abandon a half-finished render and redo it, or discard output it never commits. If your function did something the second time it should not have done, that becomes a duplicated request, a corrupted array, or a value that is wrong on screen.

code

javascript · 9 lines
javascript
function TopThreeImpure({ items }) {
  items.sort((a, b) => b.score - a.score);
  return <ul>{items.slice(0, 3).map((i) => <li key={i.id}>{i.name}</li>)}</ul>;
}

function TopThreePure({ items }) {
  const ranked = [...items].sort((a, b) => b.score - a.score);
  return <ul>{ranked.slice(0, 3).map((i) => <li key={i.id}>{i.name}</li>)}</ul>;
}

go deeper

for a junior

Know that a component body should only calculate and return elements. If a line does something to the outside world — a request, a log, a change to data you were handed — it does not belong there.

for a middle

Be able to name concrete violations and their fixes: copy before sorting, keep accumulators local, move requests and subscriptions to effects, move user-triggered work to handlers.

for a senior

Show that you recognise the symptom pattern — behaviour that depends on how many times something rendered — and that you use development double-invocation as a deliberate diagnostic rather than switching it off.

for a principal

Own purity as the contract that buys the team discardable rendering and automatic memoization; treat rule-breaking components as a systemic risk that quietly removes options from the whole codebase.

## The rule Rendering a component must be a pure calculation: **same inputs, same output, no observable effects.** The inputs are props, state, and context. The output is the element tree you return. Everything else — the DOM, the network, module scope, other components' state — is off limits while the body runs. ## What actually violates it **Mutating something that already existed.** The single most common defect: ```jsx function TopThree({ items }) { items.sort((a, b) => b.score - a.score); // mutates the caller's array return <ul>{items.slice(0, 3).map((i) => <li key={i.id}>{i.name}</li>)}</ul>; } ``` `Array.prototype.sort` sorts in place, so this reorders an array that belongs to whoever passed it — possibly an object held in a parent's state. Two renders in a row now start from different data. The fix is to copy first: `[...items].sort(...)`. **Writing to module scope.** Incrementing a counter defined outside the component, pushing into a shared array, or filling a cache during render all make the result depend on how many times the function ran — a number React does not promise you. **Touching the DOM.** Reading `element.getBoundingClientRect()` or `window.scrollY` during render gives you the *pre-update* screen, and writing to the DOM there fights the commit that is about to happen. Measurement belongs after commit. **Starting work.** `fetch`, timers, subscriptions, analytics calls, `localStorage` writes. All of these are effects, not calculations. **Updating another component's state.** Calling a parent's or sibling's setter while rendering makes one component's output depend on another's render order; React warns about it explicitly. **Non-deterministic values.** `Math.random()` and `Date.now()` in the body mean the same inputs produce different output. Sometimes that is genuinely what you want for a throwaway value, but be aware that a repeated render will not agree with the previous one. ## What is perfectly allowed Purity is about *pre-existing* values, not about mutation in general. Anything you create during this render is yours: ```jsx function Report({ rows }) { const totals = {}; // created in this render — safe to mutate for (const row of rows) { totals[row.type] = (totals[row.type] ?? 0) + row.amount; } return <Table totals={totals} />; } ``` Also allowed: deriving values, filtering, formatting, calling other pure functions, and calling hooks. Deriving instead of storing is usually the right move — a value you can compute during render does not need to be state at all. ## Why React insists React treats the render phase as speculative work. Three mechanisms depend on it: 1. **Discardable renders.** React may start rendering an update, abandon it when something more urgent arrives, and start again. If your function already sent a request, the request cannot be un-sent. 2. **Development double-invocation.** In React 18 and later, `<StrictMode>` calls component functions twice in development. Impure code shows up immediately as doubled log lines, doubled requests, or a value that differs between the two runs. That is the check, not a bug. 3. **Compile-time optimization.** The React Compiler can insert memoization automatically only because it may assume your components follow the Rules of React. A body that mutates shared state breaks that assumption, and the safe outcome is that the tool cannot help you. ## The symptoms of impurity Impure renders rarely fail loudly. They show up as bugs that feel haunted: a list that reorders itself only in development; an analytics event counted twice; a value that is correct on first load and wrong after a state update elsewhere; a total that keeps growing because a module-level accumulator was never reset. The tell is that behaviour changes depending on *how many times something rendered*, which is a quantity your code should never be able to observe. ## Where the work goes instead - Something must happen in response to a user action → **event handler**. - Something must synchronise React with an external system → **effect**, with cleanup. - A value can be computed from props and state → **compute it in the body**, purely. If you can classify the work into one of those three, you are following the rule without having to memorise the list of violations.

  • Is it impure to mutate an object during render?
    Only if the object existed before this render. Building up a local array or object inside the body and returning it is completely fine — nobody else can observe it. The rule bans mutating props, values already in state, module-level data, and anything held from a previous render, because those are shared with code outside this call.
  • Is calling Math.random() or Date.now() in a component body a purity violation?
    Strictly yes — the same props and state no longer give the same output. In development the two StrictMode runs will disagree, which is often how people notice. If the value must be stable, generate it once and keep it in state or a ref rather than recomputing it every render.
  • How would you find impure renders in an existing codebase?
    Run the app in development wrapped in StrictMode and look for doubled effects, doubled requests, or values that drift on re-render. Then read component bodies for the give-away shapes: in-place array methods on props, assignments to variables declared outside the component, DOM reads, and anything that starts asynchronous work.

saying these in an interview costs you the question

  • Sorts or pushes into a prop array inside the component body
  • Thinks purity forbids all mutation, including local objects
  • Calls fetch in the component body instead of an effect
  • Treats StrictMode's doubled logs as a React bug
  • Measures DOM nodes during render and trusts the values

context