Props and one-way data flow
Props are how data moves into a component — and the fact that data only ever flows one direction, parent to child, is not a limitation React imposes reluctantly. It's the specific design choice that makes a large React app's data flow traceable at all.
4 min read
Props: a component's input, exactly like a function's parameters
function Greeting({ name, isVip }) {
return <h1>Hello, {name}{isVip ? " (VIP)" : ""}!</h1>;
}
<Greeting name="Ada" isVip={true} />Props ("properties") are how a parent passes data into a child component — name="Ada" isVip={true} on the JSX tag become a single object, { name: "Ada", isVip: true }, received as the component function's argument. This is genuinely no different from calling any other JavaScript function with a set of named arguments (the destructuring, { name, isVip }, is just JavaScript's own object-destructuring syntax, not something React-specific at all) — a component is a function, and props are its parameters.
Props are read-only — a component can't modify its own props
function Broken({ count }) {
count = count + 1; // this "works" in the sense that JS allows it,
return <p>{count}</p>; // but it does NOT update the parent's actual value —
} // the next render just overwrites this local change againA component receiving count as a prop can read it, but reassigning the local count variable inside the function body doesn't change anything the parent actually holds — it's a local copy of the value at the moment of this specific render, and the next time the parent re-renders and passes count again, whatever local mutation happened here is simply gone, overwritten by the fresh value. Props are meant to be treated as immutable from the receiving component's perspective — genuinely changing a value that's passed down as a prop means the component that actually owns that value (the parent) needs to change it, and pass the new version down again on the next render.
One-way data flow: data only ever moves parent to child
Data flows exclusively downward, from parent to child, via props — a child component has no built-in mechanism to directly reach up and modify data owned by an ancestor. This looks restrictive at first, but it's the actual reason a large React application's data flow stays traceable at all: given any piece of data on screen, you can always find where it actually lives by tracing props upward through exactly one path, rather than hunting through an arbitrary web of components that might have mutated it from anywhere. The mechanism for a child to cause a change in a parent's data — passing a callback function down as a prop, which the child calls — is covered directly in the next lesson, once state itself is introduced.
Passing a function down as a prop: the actual way a child can trigger a parent's change
function DeleteButton({ onDelete }) {
return <button onClick={onDelete}>Delete</button>;
}
function TodoItem({ todo, onRemove }) {
return (
<li>
{todo.text}
<DeleteButton onDelete={() => onRemove(todo.id)} />
</li>
);
}A function is a completely ordinary JavaScript value, and props can hold any value at all — including a function the parent defined. DeleteButton doesn't know or care what onDelete actually does; it just calls it when clicked. This is how one-way data flow and "a child needs to cause a parent-level change" coexist without contradiction: the data itself still only flows one direction (down), but a callback flowing down as a prop is what lets a child request a change, which the parent (the actual owner of the data) decides how to handle — never a direct mutation reaching back up.
Default values and the children prop
function Button({ label = "Click me", children }) {
return <button>{children ?? label}</button>;
}
<Button /> // renders "Click me"
<Button label="Submit" /> // renders "Submit"
<Button>Custom content</Button> // children is "Custom content"label = "Click me" uses plain JavaScript default-parameter syntax (from destructuring) — nothing React-specific about it. children is genuinely special, though: it's an implicit prop React automatically populates with whatever's written between a component's opening and closing JSX tags — <Button>Custom content</Button> passes "Custom content" as children without it ever being explicitly written as children="Custom content" anywhere. This is the mechanism behind every "wrapper" component (a Card, a Modal, a Layout) that needs to render arbitrary content passed in by whoever uses it.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What happens if a component reassigns one of its own props inside its function body?
2. Why is one-way data flow (parent to child only) considered a deliberate design choice rather than a limitation?
3. How does a child component actually cause a change in data owned by its parent, given one-way data flow?