React asks one thing of your components, and it asks it firmly: while a component renders, it must behave like a pure function. This is the rule that makes the rest of React work, and breaking it produces bugs that are hard to explain. The good news is that it is easy to follow once you can see it.
What pure means
A function is pure when:
- It minds its own business. It does not change anything that existed before it was called.
- Same inputs, same output. Called with the same arguments, it always returns the same result.
Mathematical functions are pure: double(3) is always 6, and calling it changes nothing else. A recipe is like that too: the same ingredients, the same dish. In React, the "inputs" of a component are its props, its state and its context. For the same inputs, a component must return the same JSX.
Here is a pure component. Rendering it ten times gives ten identical results:
function Recipe({ drinks }) {
const cups = []; // created during this render: fine
for (let i = 0; i < drinks; i++) {
cups.push(<li key={i}>Cup of tea {i + 1}</li>);
}
return <ul>{cups}</ul>;
}
function App() {
return (
<div>
<Recipe drinks={2} />
<Recipe drinks={2} />
</div>
);
}
export default App;Cup of tea 1
Cup of tea 2
Cup of tea 1
Cup of tea 2The component builds an array and changes it with push. That is allowed because the array was created during this render. What is forbidden is changing things that already existed before the render began.
An impure component
This one counts how many times it has rendered by changing a variable outside itself:
let guest = 0; // lives outside the component
function Cup() {
guest = guest + 1; // changes outside data while rendering: impure
return <li>Tea cup for guest #{guest}</li>;
}
function App() {
return (
<ul>
<Cup />
<Cup />
<Cup />
</ul>
);
}
export default App;Tea cup for guest #2
Tea cup for guest #4
Tea cup for guest #6You might expect guests 1, 2 and 3. Because the editor mounts your app in StrictMode (as a Vite project does), React renders each component twice in development to catch exactly this mistake, and the numbers come out as 2, 4 and 6. The output depends on how many times React happened to call the function, which is out of your control. It also depends on rendering order, and React is free to render in a different order, to skip a render, or to render again later. A pure component does not care about any of that.
The fix is to pass the data in as a prop, so the output depends only on the input:
function Cup({ guest }) {
return <li>Tea cup for guest #{guest}</li>;
}
function App() {
return (
<ul>
<Cup guest={1} />
<Cup guest={2} />
<Cup guest={3} />
</ul>
);
}
export default App;Tea cup for guest #1
Tea cup for guest #2
Tea cup for guest #3Now the result is the same however often React renders.
What counts as a side effect
A side effect is anything a function does besides computing its return value. While rendering, avoid:
- Changing variables or objects created outside the component, including props (read-only).
- Reading values that change on their own and using them in the output:
Math.random(),Date.now()andnew Date()give different results each call. (Using them in an event handler or an effect is fine.) - Touching the page directly:
document.title = ...,document.getElementById(...). - Network requests, timers, logging to a server, writing to
localStorage.
An unseeded random number directly in render breaks purity: two renders give two different pages, and React may show an inconsistent mix. Compare:
function Lucky({ number }) {
return <p>Your lucky number is {number}</p>;
}
function App() {
const number = 7; // pass a stable value in; do not call Math.random() here
return <Lucky number={number} />;
}
export default App;Your lucky number is 7If you do need a random or time-based value, generate it in an event handler, store it in state, or compute it once. Those techniques are covered in the State lessons.
Local mutation is fine
Pure does not mean "never use variables or loops". Variables created during the render can be changed freely, because nobody outside can observe them, and every render starts afresh:
function Scoreboard({ scores }) {
let total = 0; // new on every render
const rows = scores.map(score => {
total += score; // local mutation: fine
return <li key={score}>{score} (running total {total})</li>;
});
return <ul>{rows}</ul>;
}
function App() {
return <Scoreboard scores={[10, 20, 30]} />;
}
export default App;10 (running total 10)
20 (running total 30)
30 (running total 60)The rule is about shared data. Mutating what you created yourself is harmless; mutating what you received (props), or what lives outside, is not. The state lessons extend this: never change an object or array that is in state or in props; copy it and change the copy.
Where side effects belong
Apps must have side effects: show a message, save a form, start a timer, change the page title. React gives them homes where they are safe:
- Event handlers. Code that runs because the user clicked, typed or submitted. It does not run during rendering, so changing things there is expected. Most side effects belong here.
- Effects (
useEffect), for things that must happen because the component appeared or changed, such as connecting to a chat server, with a cleanup when it goes away. See the effects lessons. - Refs for escape hatches such as focusing an input, also in handlers or effects.
import { useState } from "react";
function App() {
const [clicks, setClicks] = useState(0);
function handleClick() {
document.title = "Clicked " + (clicks + 1); // side effect in a handler: fine
setClicks(clicks + 1);
}
return <button onClick={handleClick}>Clicked {clicks} times</button>;
}
export default App;Clicked 1 times(The page title change happens inside the preview frame, which is why you do not see it. In a real app the browser tab would update.)
Why React insists
Purity is what allows React to do clever things on your behalf:
- Skip work. If the props have not changed, the result will not change, so React can reuse the previous output.
- Render in any order or later. Components can be rendered in the background, paused or restarted, and thrown away if the user navigates away, which only works if rendering has no lasting effect.
- Render on the server. The same component can produce HTML on a server, with no browser.
- Debug. With pure components, a bug is reproducible from the props and state alone.
StrictMode's double rendering is a free purity test: a pure component gives the same output both times, and an impure one reveals itself. If your component misbehaves in development but works in production, look for impurity first.
A checklist for any component
Ask these questions about the body of a component:
- Does it change a variable declared outside the component? If so, remove it.
- Does it change its props or anything inside them (
props.items.push)? Copy instead. - Does it call
Math.random()orDate.now()and use the result in its output? Move it. - Does it call
fetch,setTimeout,localStorageor touchdocument? Move it to a handler or effect. - Does it return the same JSX for the same props and state? It should.
Common mistakes
- Using a module-level counter or array that components change while rendering.
- Calling
Math.random()ornew Date()directly in the returned JSX. - Mutating props (
props.items.sort()), which changes the parent's data. - Fetching data in the body of the component, which would run on every render.
- Assuming a component renders exactly once. It may render many times, and twice in StrictMode.
- Thinking "pure" means "no state". State is allowed: it is one of the inputs.
Exercise
- Fix this component:
let count = 0; function Item() { count++; return <li>{count}</li>; }. Pass the number in as a prop. - The following is impure; say why, and fix it:
function Label({ items }) { items.push("end"); return <p>{items.join(",")}</p>; }. - Add a button that, when clicked, changes
document.titlefrom a handler. - Write a component that sorts a list prop for display without changing the original array.
Key points
- A pure component returns the same JSX for the same props, state and context, and changes nothing outside itself while rendering.
- React may call your component more than once, in any order, so rendering must not depend on or change outside variables.
- Create new objects and arrays during render freely, but never modify variables that existed before the render started.
- Side effects such as changing the page, sending data or starting timers belong in event handlers or, if needed, in effects.