Pure Rendering

Purity and the Rules of Rendering

React 7,897 requires component functions to be pure: given the same props, state and context, a component returns the same JSX and changes nothing outside itself on the way. During render you may not mutate props, state, module-level variables or any object that existed before the call, and you may not touch the DOM, start a request or set a timer. Creating and mutating objects you made inside the same call is fine — nobody else can see them.

An impure component renders differently every timeJavaScript
import { renderToStaticMarkup } from 'react-dom/server';
let counter = 0;
function Impure() { counter += 1; return <li>seat {counter}</li>; }
function Pure({ n }) { return <li>seat {n}</li>; }
const impure = <ul>{[1, 2, 3].map(i => <Impure key={i} />)}</ul>;
const pure = <ul>{[1, 2, 3].map(i => <Pure key={i} n={i} />)}</ul>;
for (const tree of [impure, impure, pure, pure])
  console.log(renderToStaticMarkup(tree));
Output
<ul><li>seat 1</li><li>seat 2</li><li>seat 3</li></ul>
<ul><li>seat 4</li><li>seat 5</li><li>seat 6</li></ul>
<ul><li>seat 1</li><li>seat 2</li><li>seat 3</li></ul>
<ul><li>seat 1</li><li>seat 2</li><li>seat 3</li></ul>

The same element tree gave two different results. In a browser the damage is subtler: React may render a component and throw the result away, render it twice, or pause halfway and resume later, and those liberties are what make concurrent rendering (Concurrent Rendering) and server rendering possible. <StrictMode> enforces the rule in development by calling every component function twice and discarding the first result, so an impure component shows visibly doubled counters.

Side effects belong elsewhere: in an event handler for anything a user caused, in an effect (Effects) for anything that must synchronize with an outside system. The payoff goes beyond correctness — a pure function whose inputs have not changed can be skipped entirely, which is what memo and the React Compiler exploit (Memoization and Compiler).