skip to content

React requires components to be pure during render. What does purity mean for a React component, and which kinds of code violate it?

level: middleimportance: must knowfreq 62%

answer

  1. render is a calculation, not an action
  2. same inputs, same output
  3. don't touch what you didn't create
  4. React may render twice or throw work away

basics

~20 s

A pure React component returns the same elements for the same props, state and context, and changes nothing outside itself while rendering: no mutating props or existing objects, no writing to module or global variables, no I/O. Side effects belong in event handlers or effects.

solid answer

~50 s

Purity means the render function behaves like a calculation: given the same props, state and context it produces the same output, and it leaves everything outside its own newly created values untouched. Concretely, during render you must not mutate props, state or any object that existed before this render, must not write to module-level or global variables, and must not do I/O — no `fetch`, no `localStorage`, no DOM reads or writes. Creating fresh objects and computing derived values is fine, because nobody else can observe them yet. This matters because React reserves the right to call your component when it likes: it may render and discard work, render twice in development to surface impurity, or resume a render later. If rendering has observable side effects, that freedom turns into duplicated writes and inconsistent UI. Side effects go in event handlers when they are caused by an interaction, and in effects when they synchronize with an external system.

go deeper

for a junior

Know the one-line rule: a component should only compute and return, never change things outside itself while rendering. Be able to spot a fetch call sitting in the component body.

for a middle

Explain the mechanics: which mutations are observable, why locally created objects are exempt, and why non-deterministic values in render produce output that differs between calls. Name handlers and effects as the correct destinations.

for a senior

Demonstrate diagnosis — tracing a double-counted analytics event or a list that reorders itself back to an impure render, and describing how you would prevent the class of bug across a codebase rather than patching the instance.

for a principal

Own purity as an enforceable invariant: the lint rules you require, what auto-memoization by the compiler assumes about your code, and the cost of a large legacy surface that quietly violates it.

## The contract React's rendering model rests on one promise you make: rendering is a calculation. Call the component with the same props, state and context and you get an equivalent tree of elements back, and nothing anyone else can observe has changed. React does not enforce this, which is exactly why interviewers ask about it — the failures are silent, intermittent, and blamed on React. ## What counts as impure **Mutating something that already existed.** The classic is editing a prop: ```javascript function Cart({ items }) { items.sort((a, b) => a.price - b.price); // mutates the caller's array return <List items={items} />; } ``` `sort` reorders in place, so the parent's array is now different. Any component holding that same array sees changed data without any state update, and React has no idea a change happened. The fix is to copy first — `[...items].sort(...)`, or `items.toSorted(...)`. **Writing to something outside the function.** A module-level counter incremented during render, a cache keyed by a variable declared above the component, `window.__lastRendered = props.id`. Each is invisible to React and produces different results depending on how many times render happened to run. **Reading or writing the outside world.** `fetch` in the render body, `localStorage.setItem`, `document.title = ...`, measuring an element with `getBoundingClientRect`. These are effects; render is not the place for them. **Non-deterministic values baked into the output.** `Math.random()` and `Date.now()` in render produce a different tree each time. They are the reason a value "changes for no reason" between a render that was discarded and the one that was kept, and the reason server and client output can disagree. **Mutating your own state directly.** `state.items.push(x)` during render changes the object React is still holding and skips the update path entirely. ## What is perfectly fine Creating brand-new objects and arrays inside render, computing derived values from props and state, calling pure helper functions, and mutating something you created earlier in the same render: ```javascript function Report({ rows, query }) { const visible = []; // created here for (const row of rows) { if (row.name.includes(query)) visible.push(row); // safe: local } return <Table rows={visible} />; } ``` The rule is about *pre-existing* state, not about avoiding assignment. Nobody outside this call can observe `visible` yet, so mutating it is a local implementation detail. ## Why React insists Rendering is not a single guaranteed-once call per update. React may start rendering a low-priority update, abandon it, and start again with newer state. It may render a component whose output is then thrown away because a higher-priority update arrived. In development it deliberately calls your component twice so that a duplicated side effect becomes visible immediately rather than in production. Server rendering runs your component in a different environment entirely. A pure component is indifferent to all of that; an impure one produces double-counted analytics, double-appended DOM nodes, caches that hold data from an abandoned render, and lists that reorder themselves when an unrelated sibling updates. Purity is also the precondition for optimization. Skipping a re-render whose inputs did not change is only sound if the output truly depends on nothing else — which is precisely the purity claim. The React Compiler makes this explicit: it inserts memoization automatically on the assumption that your components follow the Rules of React, and an impure component compiled that way can stop producing the effect you were secretly relying on. ## Where the side effect goes instead Ask what caused it. If a user action caused it — submitting, clicking, navigating — it belongs in the event handler, which runs after render and is allowed to be impure. If it exists to keep something outside React in sync with the current UI state — a subscription, a non-React widget, an analytics session — it belongs in an effect that runs after commit. If it produces a value you need during render, compute it from props and state instead of storing it. In an interview, saying "render is a calculation, effects and handlers are where things happen" plus one concrete violation you have actually debugged is a complete answer.

  • If mutating a pre-existing object is impure, why is mutating an array you created earlier in the same render acceptable?
    Because purity is about observable effects. An array created inside this render call has not been handed to React, to a child, or to any other component yet, so filling it before you return is indistinguishable from having built it in one expression. Once a value has escaped the current call, mutating it becomes visible to code that never asked for the change.
  • Where should a component put a call that must happen exactly once per user click, and why not in render?
    In the event handler. Handlers run in response to a real interaction, exactly once per event, and are allowed to be impure. Render can run more than once per interaction — discarded attempts, development double-invocation, a re-render triggered by a parent — so anything counted or sent from render will fire the wrong number of times.
  • Your component calls Math.random() during render to generate a DOM id. What breaks, and what would you use instead?
    The value changes on every render, so anything comparing renders sees a spurious difference, and server output cannot match client output. Use the useId hook for generated ids, or store a stable value in state or a ref if the id is data rather than markup.

saying these in an interview costs you the question

  • Thinking purity only means "no fetch in render"
  • Sorting or pushing to a props array directly in render
  • Assuming render runs exactly once per state change
  • Setting document.title or reading layout during render
  • Calling Date.now() in render and treating the result as stable

context