Props & One-Way Data Flow
Reviewed & published by Brayan K
Props are the read-only inputs a parent component passes down to a child, arriving as a single object you can destructure. Data in React flows one way — down from parent to child — so a child never edits its props; instead it calls callback props to send events back up.
Part of the free React course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Pass props from parent to child
- Destructure props with default values
- Use the special children prop
- Why props are read-only (one-way flow)
- Send events up with callback props
- Recognize prop drilling and when it hurts
1️⃣ Passing & Destructuring Props
A parent sets attributes on a child tag; React bundles them into one props object. Destructure in the parameter list to name each value, and assign defaults for optional ones so the component is safe even when a prop is missing.
function Avatar({ name, size = 48 }) {
return <img className="avatar" alt={name} width={size} />;
}
// Parent passes props as attributes:
function Profile() {
return <Avatar name="Ada" size={64} />; // size override
}// Props arrive as ONE object. Destructure to name the pieces,
// and give defaults for anything optional.
function greet({ name, greeting = "Hello" }) {
return greeting + ", " + name + "!";
}
console.log(greet({ name: "Ada" })); // Hello, Ada!
console.log(greet({ name: "Linus", greeting: "Hi" })); // Hi, Linus!
// In JSX: <Greet name="Ada" /> passes props={ name: "Ada" }2️⃣ Read-Only Props & the children Prop
Props are immutable from the child's point of view: never reassign props.x. If you need a different value, derive a new one. The special children prop holds whatever sits between the component's tags, letting you build flexible wrappers.
function Card({ children }) {
return <div className="card">{children}</div>;
}
// Anything between the tags becomes props.children:
function App() {
return (
<Card>
<h2>Title</h2>
<p>Body</p>
</Card>
);
}// Props are READ-ONLY. A child must not reassign them.
function renderUser(props) {
// props.name = "Changed"; // ❌ never do this — one-way flow
const display = props.name.toUpperCase(); // ✅ derive a NEW value instead
return display;
}
console.log(renderUser({ name: "ada" })); // ADA
console.log(renderUser({ name: "linus" })); // LINUSYour turn — destructure with a default and a safe conditional:
// 🎯 YOUR TURN — fill in each ___ then press "Try it Yourself".
// 1) DESTRUCTURE label and a DEFAULT count of 0 from the props object.
// 2) Build a badge string; only show the count when it is > 0.
function badge(___) { // 👉 { label, count = 0 }
return count > 0 ? label + " (" + count + ")" : ___; // 👉 label
}
console.log(badge({ label: "Inbox", count: 4 })); // Inbox (4)
console.log(badge({ label: "Sent" })); // Sent
// ✅ Expected output:
// Inbox (4)
// Sent3️⃣ Callbacks Up & Prop Drilling
Because data only flows down, a child reports back by calling a function prop the parent passed in — for example onClick or onChange. When a value must travel through several components that do not use it just to reach a deep child, that is prop drilling. A level or two is fine; long chains are a signal to use Context instead.
function Button({ onClick, label }) {
return <button onClick={onClick}>{label}</button>; // calls UP
}
function Toolbar({ onSave }) { // just forwards the prop
return <Button onClick={onSave} label="Save" />; // drilling
}
function Editor() {
function handleSave() { /* parent owns the action */ }
return <Toolbar onSave={handleSave} />;
}// Events flow UP via callback props. The parent owns the data
// and passes a function; the child calls it with new info.
function makeCounter() {
let total = 0; // parent's "state"
function onAdd(amount) { total += amount; } // callback passed down
onAdd(3); // child clicked "+3"
onAdd(2); // child clicked "+2"
return total;
}
console.log(makeCounter()); // 5🧩 Reorder Challenge
These lines define a child that receives a callback prop and calls it. Put them in the right order:
A. return <button onClick={onClick}>{label}</button>;
B. function ActionButton({ onClick, label }) {
C. <ActionButton onClick={handleClick} label="Go" />
D. }
E. function handleClick() { console.log("clicked"); }Common Errors (and the fix)
- Mutating a prop. — props.items.push(x) breaks one-way flow. Derive a new array or lift the change to the owner's state.
- Calling a callback instead of passing it. — onClick={handleSave()} runs immediately. Pass the function: onClick={handleSave}.
- Missing prop crashes. — Reading props.user.name when user is undefined throws. Add a default or guard.
- Forgetting children. — Content between tags vanishes unless you render {children} somewhere.
📋 Quick Reference
| Goal | Pattern |
|---|---|
| Receive props | function C({ a, b }) {} |
| Default value | { size = 48 } |
| Wrap content | {children} |
| Send event up | onClick={onSave} |
| Avoid mutation | derive a NEW value |
🧠 Quick Recall
1. Can a child reassign a prop?
No. Props are read-only. Derive a new value or lift the change to the data's owner.
2. How does a child notify its parent?
By calling a callback prop the parent passed down. Data flows down; events flow up.
3. What goes into props.children?
Whatever you nest between the component's opening and closing tags.
Mini-Challenge: Reusable Price Tag
Write one function that turns a product's props into a formatted price tag, using a default currency and an optional sale flag. Run it and check your output.
// 🎯 MINI-CHALLENGE: a reusable formatter via props
//
// Write priceTag(props) that takes { amount, currency = "$" }.
// 1. Format as currency + amount with 2 decimals.
// 2. If props.onSale is true, append " 🔥".
// 3. console.log a tag for each product.
//
// ✅ Expected output:
// $19.99
// €5.00 🔥
const products = [
{ amount: 19.99 },
{ amount: 5, currency: "€", onSale: true },
];
// your code here🎉 Lesson Complete
- ✅ Props are a component's read-only inputs
- ✅ Destructure with defaults for clean, safe components
- ✅ children lets a component wrap arbitrary content
- ✅ Data flows down; events flow up via callbacks
Practice quiz
How do props arrive in a child component?
- As separate function arguments
- As global variables
- As a single props object you can destructure
- As state
Answer: As a single props object you can destructure. React bundles all attributes into one props object; you typically destructure it in the parameter list.
Can a child component reassign one of its props?
- No, props are read-only — derive a new value instead
- Yes, props are mutable
- Only inside useEffect
- Only with setState
Answer: No, props are read-only — derive a new value instead. Props are read-only from the child's view; to change data, the owner updates its own state.
What goes into props.children?
- Only string text
- The component's state
- An array of all props
- Whatever is nested between the component's opening and closing tags
Answer: Whatever is nested between the component's opening and closing tags. props.children holds whatever content sits between the tags, enabling flexible wrappers.
In React, data flows ___ and events flow ___.
- up; down
- down; up
- both ways equally
- sideways; sideways
Answer: down; up. Data flows down from parent to child; events flow up via callback props.
How does a child notify its parent that something happened?
- By calling a callback prop the parent passed down
- By mutating a shared object
- By updating the parent's state directly
- By throwing an error
Answer: By calling a callback prop the parent passed down. The parent passes a function (callback) down; the child calls it with data. Events flow up.
How do you give a prop a default value?
- props.size || 48 only
- useState(48)
- Destructure with a default: { size = 48 }
- defaultProps is the only way
Answer: Destructure with a default: { size = 48 }. Destructuring defaults like { size = 48 } fill in missing optional props cleanly.
What is 'prop drilling'?
- Mutating a prop deep in the tree
- Passing a prop through many intermediate components that don't use it to reach a deep child
- Using too many default props
- Calling a callback prop
Answer: Passing a prop through many intermediate components that don't use it to reach a deep child. Threading a prop through layers that don't need it; long chains are a signal to use Context.
Which correctly passes a handler so it runs on click, not immediately?
- onClick={handleSave()}
- onClick=handleSave
- onClick={() handleSave}
- onClick={handleSave}
Answer: onClick={handleSave}. Pass the function reference; adding () calls it immediately during render.
A long chain of prop drilling is a signal to reach for…
- More default props
- Context (or a state library)
- Mutating props
- Class components
Answer: Context (or a state library). When a value travels through many components that don't use it, Context avoids the drilling.
If you need a different value than a prop provides, you should…
- Reassign the prop
- Store it in the prop object
- Derive a new value from it
- Mutate props.items with push
Answer: Derive a new value from it. Never mutate props; derive a new value (e.g. props.name.toUpperCase()) to keep one-way flow.
Continue this course
- Previous: State and Lifecycle
- Next: Fragments & Multiple Elements — Return several elements without wrapper-div soup using <></> and keyed fragments
- Quick reference: React cheat sheet
Frequently asked questions
Can a child component change its props?
No. Props are read-only. A child may read them but must never reassign them. To change data, the owner of that data updates its own state and passes the new value down.
What is the children prop?
Whatever you place between a component's opening and closing tags is passed as props.children. It lets a component wrap arbitrary content, like <Card>anything here</Card>.
How does a child tell a parent something changed?
The parent passes a function down as a prop (a callback). The child calls that function with data. This keeps data flowing one way while events flow up.
What is prop drilling and when does it hurt?
Prop drilling is passing a prop through many intermediate components that do not use it, just to reach a deep child. It hurts when the chain is long; then reach for Context or a state library.