Props: data from above
In short: Props are the data a component gets from its parent, the way a function gets parameters; the child does not change them, and children passes in content from outside.
Problem
Bread three times
The catalog must show three products: bread, milk and cheese. The product card is ready from the last lesson, so App puts it in three times.
Console
All three cards are bread. The product is written inside ProductCard, and every card shows it. The other way is BreadCard, MilkCard and CheeseCard: three copies of the same component, and every change to the card has to be made three times. What is missing is a way to tell the card which product to show.
The idea
Props: data from above
A component is a function. A function gets its data as parameters, and a component gets them as attributes from its parent: the component that uses it. These attributes are called props (from properties). The parent writes them, the child reads them.
<ProductCard product={bread} /><ProductCard product={milk} />export default function ProductCard({ product }) { return <h2>{product.name}</h2>;}React collects all attributes into one object and passes it to the function: { product: bread }. The notation ({ product }) takes the property product out of it (destructuring). Without the braces the parameter is the whole object: function ProductCard(props), and then props.product.name.
There is one card, and every use has different data. A function works the same way: one definition, many calls with different arguments.
See it
See how they flow
const bread = { name: 'White bread', price: 1.2 };const milk = { name: 'Fresh milk', price: 1.6 };const cheese = { name: 'White cheese', price: 7.9 };
function ProductCard({ product, discount = 0 }) { return ( <article> <h2>{product.name}</h2> <p>Price: {money(product.price * (1 - discount / 100))}</p> </article> );}
export default function App() { return ( <main> <h1>Bread and milk</h1> <div> <ProductCard product={bread} /> <ProductCard product={milk} /> <ProductCard product={cheese} discount={10} /> </div> </main> );}Step by step, in words
- App holds the data: three products. Below it are three ProductCards: the same component three times, and none of them knows yet what to show.
- product={bread} hands the object bread to the first card. The attributes of a component are called props.
- The other two cards get their own product. The third one also gets discount = 10. Each card has its own props.
- Each card draws itself from its props. Bread and milk got no discount, so it is 0 by default. The cheese is 10% off: €7.11.
- If the card tries props.discount = 0, nothing goes up: in development React throws a TypeError (the props object is frozen), and App’s data stay the same.
- Data flow one way only: from the parent down to the child. To change something above, the child has to ask the parent. That is the module "State".
Every card knows only its own props. It does not see the other cards, and it does not know where the data in App come from.
See it
One card, three products
Console
The products are in App, and there is one ProductCard. Add a fourth product in App (the image img/apples.svg exists) and one more <ProductCard … />. You do not touch the card. With 30 products you will want a list rather than 30 lines: that is the next lesson.
Try it
Find the bug
The card does not show, and the console says "TypeError: Cannot read properties of undefined (reading 'toFixed')".
Pick the line with the bug.
product="bread" passes the text "bread", not the object. Text has no price, so product.price is undefined and money fails on toFixed. The object is passed in curly braces: product={bread}.The idea
A default value
Some products are on sale. The card gets a second prop, discount, in percent. Most products have no discount, and it is not nice for every line in App to write discount={0}. A missing prop is undefined, like a missing function argument. The price becomes price * (1 - undefined / 100), which is NaN.
So the prop gets a default value:
export default function ProductCard({ product, discount = 0 }) {This is a default value in JavaScript destructuring: if discount is missing, it is 0. In older code you will see ProductCard.defaultProps = { discount: 0 } and propTypes. React 19 no longer looks at them for function components: checking types is the job of TypeScript.
Try it
A discount
App gives the cheese a discount of 10 (percent). Make the card take discount, 0 by default, and show the price with the discount: the bread €1.20, the milk €1.60, the cheese €7.11.
Console
Solution
ProductCard.jsxconst money = (x) => '€' + x.toFixed(2);
export default function ProductCard({ product, discount = 0 }) {
return (
<article>
<h2>{product.name}</h2>
<p>Price: {money(product.price * (1 - discount / 100))}</p>
</article>
);
}
App.jsximport ProductCard from './ProductCard';
const bread = { name: 'White bread', price: 1.2 };
const milk = { name: 'Fresh milk', price: 1.6 };
const cheese = { name: 'White cheese', price: 7.9 };
export default function App() {
return (
<main>
<h1>Bread and milk</h1>
<div>
<ProductCard product={bread} />
<ProductCard product={milk} />
<ProductCard product={cheese} discount={10} />
</div>
</main>
);
}
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
h1 { font-size: 22px; margin: 0 0 12px; }
main > div { display: flex; flex-wrap: wrap; gap: 12px; }
article { border: 1px solid #ddd5c4; border-radius: 8px; padding: 8px 12px; width: 140px; }
article img { width: 56px; height: 56px; }
h2 { font-size: 16px; margin: 4px 0; }
p { margin: 4px 0; }
.card { border: 1px solid #ddd5c4; border-radius: 8px; background: #fbf8f2; padding: 4px 12px; margin-top: 12px; max-width: 320px; }
button { font: inherit; margin-left: 8px; }
function ProductCard({ product, discount = 0 }) and the price money(product.price * (1 - discount / 100)). Without = 0 the bread and the milk show "€NaN": the check looks at all three prices.
The idea
Read-only
The data belong to whoever holds them. The card can read its props, but it cannot change them. Try the "One more" button: the child tries to increase its own qty.
Console
The quantity stays 2, and the console says "TypeError: Cannot assign to read only property 'qty' of object". In development mode React freezes the props object, exactly to catch code like this.
Even if it went through, the change would be only in the child: App would still say 2, and on the next drawing App would pass 2 again. Two places with different truths are the bug from "A list from data". The rule: data flow down, and changing them is the parent’s job. How a child asks its parent to change something is the module "State".
The idea
Content from outside
The shop has frames that look the same with different things inside: "Delivery" with two paragraphs, "Returns" with one. The title is a prop. The content is a whole piece of HTML, and it is handier to write it between the opening and the closing tag, as with <section>.
<Card title="Delivery"> <p>Within 2 working days.</p></Card>export default function Card({ title, children }) { return ( <section className="card"> <h2>{title}</h2> {children} </section> );}Everything between <Card> and </Card> arrives in the special prop children. Card decides where to put it.
Console
Try it
The frame
Card shows only the title. Make it also show the content that App writes between <Card> and </Card>, under the title.
Console
Solution
Card.jsxexport default function Card({ title, children }) {
return (
<section className="card">
<h2>{title}</h2>
{children}
</section>
);
}
App.jsximport Card from './Card';
export default function App() {
return (
<main>
<h1>The cart</h1>
<Card title="Delivery">
<p>Within 2 working days.</p>
<p>Free over €30.</p>
</Card>
<Card title="Returns">
<p>Within 14 days of delivery.</p>
</Card>
</main>
);
}
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
h1 { font-size: 22px; margin: 0 0 12px; }
main > div { display: flex; flex-wrap: wrap; gap: 12px; }
article { border: 1px solid #ddd5c4; border-radius: 8px; padding: 8px 12px; width: 140px; }
article img { width: 56px; height: 56px; }
h2 { font-size: 16px; margin: 4px 0; }
p { margin: 4px 0; }
.card { border: 1px solid #ddd5c4; border-radius: 8px; background: #fbf8f2; padding: 4px 12px; margin-top: 12px; max-width: 320px; }
button { font: inherit; margin-left: 8px; }
Card also takes children: function Card({ title, children }), and puts it under the title: {children}. Without the curly braces the page shows the word "children". If you put it inside <h2>, the paragraphs become part of the heading, and it is no longer "Delivery".
More precisely, if you are curious
More than one place for content. There is one children. If the frame also has a bottom part, it is one more prop with JSX: <Card title="Delivery" footer={<a href="/delivery">Details</a>}>…</Card>, and Card puts {footer} where it belongs.
All properties at once. <ProductCard {...product} /> passes every property of the object as a separate prop: name, price, image. It is handy, but you cannot see what exactly the card gets.
Only the first level is frozen. props.qty = 3 throws an error, but props.product.price = 2 goes through silently: the product object is not frozen, it is the same object as in App. That way the child changes the parent’s data behind its back. Do not do it.
Types. React 19 no longer checks propTypes of function components and does not use defaultProps for them. The types of props are written with TypeScript: it is in the lesson on a real project.
Exercise
The shop window
Build the shop window from scratch. ProductCard takes product and discount (0 by default) and shows the image (alt is the name), the name as a level-two heading and "Price: …" with the discount. Card takes title and shows the title (level two) and the content from outside. In App, under the heading: the three cards in a div (the cheese with discount 10), then a Card "Delivery" with "Within 2 working days." and "Free over €30.".
Console
Solution
App.jsximport ProductCard from './ProductCard';
import Card from './Card';
const bread = { name: 'White bread', price: 1.2, image: 'img/bread.svg' };
const milk = { name: 'Fresh milk', price: 1.6, image: 'img/milk.svg' };
const cheese = { name: 'White cheese', price: 7.9, image: 'img/cheese.svg' };
export default function App() {
return (
<main>
<h1>The shop window</h1>
<div>
<ProductCard product={bread} />
<ProductCard product={milk} />
<ProductCard product={cheese} discount={10} />
</div>
<Card title="Delivery">
<p>Within 2 working days.</p>
<p>Free over €30.</p>
</Card>
</main>
);
}
ProductCard.jsxconst money = (x) => '€' + x.toFixed(2);
export default function ProductCard({ product, discount = 0 }) {
return (
<article>
<img src={product.image} alt={product.name} />
<h2>{product.name}</h2>
<p>Price: {money(product.price * (1 - discount / 100))}</p>
</article>
);
}
Card.jsxexport default function Card({ title, children }) {
return (
<section className="card">
<h2>{title}</h2>
{children}
</section>
);
}
style.cssbody { font-family: system-ui, sans-serif; margin: 16px; }
h1 { font-size: 22px; margin: 0 0 12px; }
main > div { display: flex; flex-wrap: wrap; gap: 12px; }
article { border: 1px solid #ddd5c4; border-radius: 8px; padding: 8px 12px; width: 140px; }
article img { width: 56px; height: 56px; }
h2 { font-size: 16px; margin: 4px 0; }
p { margin: 4px 0; }
.card { border: 1px solid #ddd5c4; border-radius: 8px; background: #fbf8f2; padding: 4px 12px; margin-top: 12px; max-width: 320px; }
button { font: inherit; margin-left: 8px; }
ProductCard: function ProductCard({ product, discount = 0 }), <img src={product.image} alt={product.name} />, <h2>{product.name}</h2> and the price with the discount. Card: function Card({ title, children }) with <section className="card">, <h2>{title}</h2> and {children}. App: <ProductCard product={cheese} discount={10} /> and the content between <Card title="Delivery"> and </Card>. The check finds the images by alt, the prices of the three cards and the paragraphs inside .card.
Test
Check yourself
Related lessons
Section titled "Related lessons"- Builds on: Text, links, images, tables · Accessibility · A list from data · The first component
- Continues in: Lists and keys · Conditions: empty, error, loading · State · Derived state
- Tasks on this topic: Components