Runnable lesson · press Try it on the examples to edit and run them here.
React components should behave like pure functions with respect to their props: given the same props, return the same JSX, and never change the props themselves. The rule is: props are read-only.
What not to do
function Order({ items }) {
items.push("free gift"); // mutates the array that the parent owns
return <p>{items.length} items</p>;
}
function App() {
const items = ["book", "pen"];
return (
<div>
<Order items={items} />
<Order items={items} />
</div>
);
}
Each render pushes another item into the same array that App created. The first Order shows 3 and the second 4, and the numbers grow every time React renders. The child silently changed data it does not own.
What to do instead
Create a new value from the prop and leave the original alone:
function Order({ items }) {
const withGift = [...items, "free gift"]; // a new array
return <p>{withGift.length} items</p>;
}
function App() {
const items = ["book", "pen"];
return (
<div>
<Order items={items} />
<Order items={items} />
</div>
);
}
Both components show 3 items, always. Spread (...), map, filter, slice and Object.assign({}, obj) all make new values. See Avoiding State Mutation for the full toolbox.
Why is it a rule?
- Predictability. A component whose output depends only on its props is easy to reason about and to test.
- Rendering can happen any time. React may call your component more than once (Strict Mode does it on purpose in development). Mutations during render then repeat.
- Optimisation depends on it. React and tools such as
memoassume that “same props object means same output”.
What if I need to change it?
If a child needs to change data, the owner must do it. Pass a function down and call it (see Passing Functions as Props), or keep a local copy in state when the child really needs its own editable version.
function Editor({ initialText }) {
// The child needs its own editable copy: local state, starting from the prop.
const [text, setText] = React.useState(initialText);
return (
<div>
<input aria-label="Draft" value={text} onChange={(e) => setText(e.target.value)} />
<p>Draft: {text}</p>
</div>
);
}
function App() {
return <Editor initialText="Hello" />;
}
Naming the prop initialText tells readers that later changes to it are ignored; it is only the starting value.
Key points
- Never change a prop or anything inside it (push, assign, delete).
- Create a changed copy instead.
- If a child needs to change the data, call a function from the parent or keep local state.