Props: data goes down
Passing values into a component, destructuring them in the parameter, filling a hole with children, and the rule that props are read-only.
A component with a gap in it
A component that always renders the same words is a template. Props are what turn it into a tool: <Card title="Hello" /> passes a title value in, and inside the component it arrives as one object — function Card(props) — with props.title on it.
The parameter is almost always destructured on the spot: function Card({ title, tone }). It reads like a list of what the component needs, which is the most useful documentation a component can have.
Children, and defaults
Anything written between an opening and closing tag arrives in a prop called children: <Panel>Some words</Panel> gives the component props.children, ready to be placed somewhere in its own JSX. That is how wrapper components work — they own the frame, the caller owns the contents.
A default belongs in the destructuring: function Badge({ tone = "plain" }) means the component works when the prop is missing. A missing prop you do not default is simply undefined, so a quick default beats a crash or an odd "undefined" on the page.
Read-only, both ways
Props are read-only in the strict sense: a component must not assign to them. The value belongs to whoever rendered the component, and changing it there is how a child asks for a different picture — that road leads to state, two chapters from here.
Downwards it is just JavaScript: any value can be a prop — a string, a number, a boolean, an object, an array, even a function. A boolean written plainly is true: <Badge loud /> is the same as <Badge loud={true} />.
Props are the component’s arguments
A component is a function, and props are its arguments. Everything said about arguments elsewhere applies here: the order does not matter because the names do, a missing one is undefined unless defaulted, and the component cannot change what it was given any more than a function can change the number it was called with.
The difference is who calls it. You write <Card title="Hi" /> and React does the call, at a moment you do not control and as often as the tree needs. That is why a prop is a description of the current picture rather than a message sent once.
Spreading a props object
When a wrapper passes most of its props straight through, the spread saves the typing: <Card {...props} /> hands every field on. It is convenient and it hides the contract — a reader can no longer see what Card needs — so use it for a pass-through wrapper and write the names out everywhere else.
Destructured, defaulted, filled
function Badge({ tone = 'plain' }) {
return <span className={'badge badge--' + tone}>new</span>;
}
function Panel({ title, children }) {
return (
<section className="panel">
<h3>{title}</h3>
<div>{children}</div>
</section>
);
}
function App() {
return (
<Panel title="Today's lesson">
<p>Props are read-only, and they travel one way: down.</p>
<Badge />
<Badge tone="warm" />
</Panel>
);
}Props are a snapshot
Every render, React calls the component with a fresh props object. The object is not a live channel: it describes what the parent asked for at that moment. If the parent later changes a value, the child is called again with a new object; the old one never changes.
That is why mutating a prop is pointless as well as wrong. The change lives until the next render and then disappears, because the next object comes from the parent. When a child needs to remember something, it needs state, not a modified prop.
Values, not strings
A prop written in quotes is always a string, even when it looks like a number: count="3" gives the child the text "3". Braces pass the value itself: count={3} passes a number, and items={books} passes the array rather than the word books.
Booleans have a shorthand: disabled is disabled={true}, and disabled={false} is the same as leaving it out. For everything else — objects, arrays, functions, null — braces are the honest form, and they make the type visible at the call site.
Three prop mistakes worth recognising: assigning to props.title inside the child, which React overwrites on the next render; forgetting a default and printing undefined; and passing a number or a boolean in quotes, so the child compares a string with a number and quietly gets the wrong answer.
Try these next
- Give
Cardayearprop as a number and render it only when it is above 2000. - Add a
compactboolean prop and change the card padding when it is true. - Render a card with no
noteand confirm the default appears.
Cards that take what they are given
Run it, then add a `note` prop to Card that renders in small text under the title. Then render a third card with no note and give it a sensible default.
Price tags with defaults
Run it, then add a fourth price with `sale` set and no `currency`, so the default has to appear. Then change the default itself and check every tag that omitted it.
A component that assigns to its own props changes a value somebody else owns. React does not stop you; the next render simply overwrites it, and the bug looks like the page ignoring a click.
Check what you learned
One question at a time. A wrong answer explains itself — take another go.
Part 1 of 5
What may a component do with its props?
