Why React Components Must Be Pure
Props are immutable because mutating them is a side effect. React builds on pure functions because purity is what makes the component model predictable and safe to optimize.
I knew props were read-only before I understood why.
"Don't mutate props" felt like a rule handed down from framework designers who wanted to make things complicated. Then I looked at what would actually happen if you did mutate them.
The rule became obvious.
Props Is Just an Object
When React calls your component, it passes a props object as the argument. That object contains all the values the parent passed down.
function Pizza({ name, price }: { name: string; price: number }) {
// props is: { name: "Margherita", price: 10 }
}Now, what if you changed price directly inside the component?
function Pizza({ name, price }: { name: string; price: number }) {
price = price + 5; // don't do this
}In isolation this looks harmless. But props is a reference to an object that lives in the parent. When you modify it, you are modifying the parent's data.
That is a side effect. You changed something outside the scope of your own function.
What a Side Effect Is
A pure function has one rule: given the same inputs, it always returns the same output, and it changes nothing outside itself.
A side effect is anything that breaks that rule: reading or writing to something outside the function scope. Network requests, timers, DOM mutations, and mutating a prop object the parent handed you all qualify.
React requires components to be pure with respect to props and state. The component can use them, read them, derive values from them. It cannot reach outside itself and change them.
// Pure: reads props, returns JSX, touches nothing else
function Badge({ label }: { label: string }) {
return <span className="badge">{label}</span>;
}
// Impure: modifies the object it received
function Badge({ style }: { style: React.CSSProperties }) {
style.color = 'red'; // mutates caller's object
return <span style={style}>badge</span>;
}The second component is broken. The parent passed a style object expecting it to come back unchanged. Any other component using the same object now has a surprise red color.
Why React Needs Purity
Purity is not just a rule for your own sanity. It is what makes React's optimization model work.
React can re-render a component at any time: on every state change, on every parent render. If components had side effects on their inputs, rendering would be unpredictable.
The result of a render would depend on how many times it had been called, what it had changed on previous calls, and what order the renders happened in.
Purity means every render is self-contained. React can throw away renders, run them twice (it does this in Strict Mode), or skip them entirely, and the app still works correctly because no single render leaves behind damage.
One-Way Data Flow Is the Same Principle
React enforces that data only flows from parent to child. A child component cannot push data up to its parent directly.
This is not a limitation. It is purity at the system level.
If any component could reach up and modify a parent's state, you would have the same problem as DOM-as-state-storage: multiple things writing to the same data, in no predictable order, with no single owner.
One-way data flow means every piece of state has exactly one owner. That owner decides when and how it changes. Everything downstream just reads.
App (owns data)
down props
Header (reads)
down props
Nav (reads)Data flows down. Events flow up, but through callbacks the parent explicitly passed down as props, not through direct mutation. The parent stays in control.
State Is the Escape Valve
Props being immutable raises an obvious question: how does anything ever change?
The answer is state. State is data the component owns and can update. When state updates, React re-renders the component with the new value.
The difference from props:
| Props | State | |
|---|---|---|
| Owned by | Parent | The component itself |
| Can be changed by | Parent only | The component itself |
| What triggers re-render | Parent re-renders | setState call |
When a child needs to trigger a change in the parent, the parent passes down a callback function as a prop. The child calls it. The parent updates its own state. React re-renders.
This is lifting state up. It is how React keeps one-way flow intact even when data needs to travel upward.
The Rule in One Sentence
A component is a pure function of its props and state: given the same inputs, it always renders the same output, and it never modifies anything outside itself.
Everything in React is built on the assumption that this rule holds: the reconciler, Strict Mode's double-mount, server components, concurrent rendering.
The Essentials
- Props are immutable because they are a reference to the parent's data. Mutating props is a side effect: you change something outside your function scope, which corrupts the parent's data unexpectedly.
- React requires pure components because purity makes renders safe to repeat, reorder, or skip. An impure component with side effects on its inputs produces unpredictable results when React re-renders.
- One-way data flow is purity at the architecture level. Every piece of state has one owner. Data flows down as props. Changes flow up through callbacks. Nothing writes to another component's state directly.
Further Reading and Watching
- Keeping Components Pure -- React Docs: The official explanation of why purity matters and how Strict Mode surfaces impure components.
- You Might Not Need an Effect -- React Docs: Once the purity model clicks, many effects turn out to be unnecessary derivations in disguise.
Keep reading