You've been dragging the same limitation through two lessons now: CicloUrbano's catalogue shows three identical cards because the bike data is written inside BikeCard. A component like that isn't reusable: it can only ever show that one specific bike. Props are the fix, and they're one of the two or three truly fundamental concepts in React. A prop is a piece of data a component receives from outside, from whoever uses it. Props turn a rigid component into a parameterizable template, define the contract between parent and child, and are the reason you can write BikeCard once and use it on five different screens. In this lesson you'll learn to pass and receive them, what kinds of values they accept, how to give them default values, what the special children prop is, why they're read-only, and how to document your components' contract. By the end, the catalogue will finally show different bikes.
Contents
- What props are and what problem they solve
- Passing and receiving props
- Destructuring in the component signature
- What kinds of values can be passed
- Default values
- The special
childrenprop - Props are read-only
- Spreading props with the spread operator
- Documenting a component's contract
- CicloUrbano's catalogue, now parameterized
- What props are and what problem they solve
Props comes from properties. They're the data that flows from a parent component down to a child component through JSX attributes.
The most useful analogy is a function: a component is a function, and props are its arguments.
// A function with no parameters can only do one thing
function greet() {
return 'Hello, Ana Ribera';
}
// A function with parameters works for infinite cases
function greet(name) {
return `Hello, ${name}`;
}Exactly the same thing happens with components:
// Component with no props: it can only show one bike
function BikeCard() {
return <h3>Classic Urban</h3>;
}
// Component with props: it works for any bike
function BikeCard(props) {
return <h3>{props.model}</h3>;
}The analogy isn't a coincidence: props are literally the function component's arguments. React collects the attributes you write in the JSX, puts them into an object, and passes that object to your function as its first parameter.
flowchart LR
A["App<br/>writes the JSX"] -- "model='Classic Urban'" --> B["React<br/>builds { model: 'Classic Urban' }"]
B -- "passes it as an argument" --> C["BikeCard(props)<br/>uses it in its JSX"]
And a very important structural property: the flow is one-way and flows down the tree. A parent passes data to a child; a child never "returns" data by writing to its props. That restriction, which looks limiting at first, is exactly what makes a React application predictable: to find out where a piece of data comes from, you go up the tree; you never have to hunt for who changed it behind your back.
- Passing and receiving props
Passing them: attributes in the JSX
Two syntaxes coexist, and the difference is the same one you already saw in the JSX lesson (01-04):
| Syntax | When to use it | Example |
|---|---|---|
| Quotes | Only for literal string values | model="Classic Urban" |
| Braces | For anything else: numbers, booleans, objects, arrays, variables, expressions | pricePerHour={2.5} |
A classic mistake: pricePerHour="2.5" passes the string "2.5", not the number. And "2.5".toFixed(2) doesn't exist, so the card will crash. If the value isn't literal text, it goes in braces.
Receiving them: the props parameter
// src/components/BikeCard.jsx
function BikeCard(props) {
return (
<article className="bike-card">
<h3>{props.model}</h3>
<p>
<strong>€{props.pricePerHour.toFixed(2)} / hour</strong>
</p>
</article>
);
}
export default BikeCard;React calls your function with a single object that holds every attribute you wrote:
// What React builds internally and passes to the function
{ model: 'Classic Urban', pricePerHour: 2.5 }Three details worth nailing down from the start:
- The parameter can be called whatever you like.
propsis the universal convention, but it's an ordinary parameter. Call itprops. - You choose the names of the props yourself. There's no list of valid props: they're your component's vocabulary. Use camelCase (
pricePerHour,stationId), just like the rest of JavaScript. - If you don't pass an attribute, that prop is
undefined. It doesn't throw an error; it simply isn't there. That's why we'll look at default values further down.
- Destructuring in the component signature
Writing props. in front of every value gets noisy. ES6 destructuring lets you pull the props out directly in the function signature, and it's the dominant form in modern React.
// With props.
function BikeCard(props) {
return (
<article className="bike-card">
<h3>{props.model}</h3>
<p>Type: {props.type}</p>
<p>Status: {props.status}</p>
</article>
);
}// With destructuring in the signature <- recommended form
function BikeCard({ model, type, status }) {
return (
<article className="bike-card">
<h3>{model}</h3>
<p>Type: {type}</p>
<p>Status: {status}</p>
</article>
);
}The braces in ({ model, type, status }) are not an object: they're destructuring syntax applied to the parameter. It's equivalent to writing const { model, type, status } = props; on the first line of the body.
Why it's better:
| Advantage | Explanation |
|---|---|
| Visible contract | The signature states exactly what the component expects. It reads like documentation |
| Less noise | The props. prefix disappears from the whole JSX |
| Earlier errors | Typo a name in the signature, and you catch it right where it matters |
| Simple default values | You write them right there, as you'll see in section 5 |
Nested and partial destructuring
In CicloUrbano we're going to pass the entire bike as an object, not field by field. You can destructure it at two levels:
// We receive the 'bike' prop and use it as an object
function BikeCard({ bike }) {
return <h3>{bike.model}</h3>;
}
// Or we also extract its fields (nested destructuring)
function BikeCard({ bike: { model, type, status } }) {
return <h3>{model}</h3>;
}The second form is valid, but it loses the reference to the whole object and becomes unreadable once there are many fields. In the course we'll use the first one: receive bike and, if needed, destructure it in the body.
- What kinds of values can be passed
Any JavaScript value. Literally any.
| Type | How it's passed | Example in CicloUrbano |
|---|---|---|
| String | Quotes or braces | model="Classic Urban" |
| Number | Always braces | pricePerHour={2.5} |
| Boolean | Braces, or the bare-attribute shortcut | featured={true} or featured |
| Object | Braces (double, if it's a literal) | bike={bikes[0]} · bike={{ id: 'bici-001' }} |
| Array | Braces | types={['urbana', 'electrica']} |
| Function | Braces, without parentheses | onBook={bookBike} |
| JSX element | Braces | icon={<StatusBadge status="disponible" />} |
null / undefined |
Braces | station={null} |
Let's look at the cases that cause the most confusion.
Booleans and the shortcut
<BikeCard bike={bike} featured={true} />
<BikeCard bike={bike} featured /> {/* Identical to the line above */}
<BikeCard bike={bike} featured={false} />
<BikeCard bike={bike} /> {/* featured is undefined, not false */}Writing the bare attribute is equivalent to ={true}, just like in HTML with disabled or checked. Watch out for the last line: omitting the prop gives undefined, which is falsy but isn't false. In practice both behave the same in a condition, but they aren't the same value.
Objects and the double brace
{/* We pass a variable that's already an object: one pair of braces */}
<BikeCard bike={bikes[0]} />
{/* We write the object right there: expression braces + object braces */}
<BikeCard bike={{ id: 'bici-001', model: 'Classic Urban' }} />It's the same double brace you already saw in style={{ color: 'red' }}: the outer ones open a JSX expression, the inner ones are the object literal.
Functions: no parentheses
{/* CORRECT: we pass the function */}
<BikeCard bike={bike} onBook={bookBike} />
{/* WRONG: we execute it right now and pass its result */}
<BikeCard bike={bike} onBook={bookBike()} />Passing functions as props is the mechanism a child uses to let its parent know something happened. Here you just need to know it's possible and this is the syntax; the full child → parent communication pattern is covered in Lifting State Up, and event handlers in Handling Events.
JSX elements as props
A prop can hold interface. That opens the door to very flexible components:
- Default values
If the parent doesn't pass a prop, it arrives as undefined. To keep the component working, you give it default values with JavaScript's default parameters, right in the destructuring:
// src/components/StatusBadge.jsx
function StatusBadge({ status = 'disponible', compact = false }) {
const classes = compact
? `status status--${status} status--compact`
: `status status--${status}`;
return <span className={classes}>{status}</span>;
}
export default StatusBadge;<StatusBadge status="alquilada" /> {/* shows 'alquilada', not compact */}
<StatusBadge /> {/* shows 'disponible' by default */}
<StatusBadge status="carga" compact />One nuance that catches people off guard: the default value applies only when the prop is undefined, not when it's null, 0, or an empty string.
<StatusBadge status={null} /> {/* status is null, NOT 'disponible' */}
<StatusBadge status="" /> {/* status is '', NOT 'disponible' */}A note on defaultProps
In older code you'll see this other form:
// OLD FORM — don't use it in function components
StatusBadge.defaultProps = {
status: 'disponible'
};defaultProps has been removed for function components as of React 19. If you find it, it's older code, and you should translate it to default parameters. It still works on class components, because a class has no signature where default values could be written.
- The special
children prop
children propThere's one prop that isn't passed as an attribute: everything you write between a component's opening and closing tags arrives in the children prop.
<Panel title="Available bikes">
<p>Everything in here is children</p>
<BikeCard bike={bikes[0]} />
</Panel>The component that receives it:
// src/components/Panel.jsx
function Panel({ title, children }) {
return (
<section className="panel">
<h2 className="panel__title">{title}</h2>
<div className="panel__body">{children}</div>
</section>
);
}
export default Panel;children is the foundation of the composition pattern: you write components that contribute structure or behavior and let whoever uses them decide the content.
// src/App.jsx (excerpt)
<Panel title="Downtown bikes">
<BikeCard bike={bikes[0]} />
<BikeCard bike={bikes[1]} />
</Panel>
<Panel title="Notice">
<p>The cargo bike service is under maintenance until Monday.</p>
</Panel>One Panel, two completely different contents. Without children you'd have to invent a prop for every possible case, or duplicate the component.
Things worth knowing about children:
- It can be anything: text, one element, several elements, an array, or nothing (
undefined). childrencan't also be passed as an attribute. If you write<Panel children="hi">bye</Panel>, the content between the tags wins.- A component can have several content "zones" by passing JSX elements through named props (
header={<…/>},footer={<…/>}). This is sometimes called slots.
This pattern has a lot more mileage — generic containers, wrappers, component specialization — and gets covered in depth in Composition vs Inheritance.
- Props are read-only
This is the single most important rule in the lesson, and it has no exceptions:
A component must never modify its own props. React operates on the premise that a component, given the same props, always produces the same result. That's called being a pure function.
// FORBIDDEN
function BikeCard({ bike }) {
bike.status = 'alquilada'; // mutates the parent's object
return <h3>{bike.model}</h3>;
}// FORBIDDEN
function BikeCard(props) {
props.model = props.model.toUpperCase(); // direct assignment to props
return <h3>{props.model}</h3>;
}What exactly happens if you try it
It depends on what you're mutating, and neither scenario is good:
| Case | What happens |
|---|---|
props.something = … |
React freezes the props object in development: the assignment fails in strict mode with TypeError: Cannot assign to read only property |
props.object.field = … |
No error at all. You're mutating the parent's original object, which React never froze. Worse still: React never finds out about the change, so no render is triggered, and the screen can end up out of sync with the data |
The second case is the dangerous one: silent, hard to debug, and capable of producing "data that changes without anyone repainting it." In CicloUrbano, mutating bike.status inside the card would modify the bikes array imported from domain.js for the entire application.
What to do instead
- If you just need a transformed value to paint, compute a local variable. That's perfectly fine: what's forbidden is writing to the prop, not reading it or deriving from it.
function BikeCard({ bike }) {
const upperCaseModel = bike.model.toUpperCase(); // ✅ new variable
const formattedPrice = bike.pricePerHour.toFixed(2); // ✅
return (
<article className="bike-card">
<h3>{upperCaseModel}</h3>
<p><strong>€{formattedPrice} / hour</strong></p>
</article>
);
}- If the data genuinely needs to change, it isn't a prop: it's state, and you'll see that in the next lesson. And if the change originates in the child but the data belongs to the parent, the child notifies it through a function received via props (04-01).
- Spreading props with the spread operator
The spread operator (...) lets you pass every property of an object as props in one go:
const bike = { id: 'bici-001', model: 'Classic Urban', type: 'urbana', status: 'disponible', stationId: 'est-01', pricePerHour: 2.5 };
// Writing this...
<BikeCard {...bike} />
// ...is exactly equivalent to this:
<BikeCard
id={bike.id}
model={bike.model}
type={bike.type}
status={bike.status}
stationId={bike.stationId}
pricePerHour={bike.pricePerHour}
/>And the component receives them as loose props:
It can also be combined with explicit props, and order matters: whatever's written later wins.
<BikeCard {...bike} status="mantenimiento" /> {/* status = 'mantenimiento' */}
<BikeCard status="mantenimiento" {...bike} /> {/* status = 'disponible', the spread overrides it */}When NOT to use it
It's a convenient tool that's easy to overuse. The downsides:
| Problem | Explanation |
|---|---|
| The contract disappears | Reading <BikeCard {...bike} /> doesn't tell you which props the component actually receives. You need to open two files to find out |
| Extra props leak through | Every key from the object gets passed, including ones the component doesn't use (id, stationId). If they ever get forwarded to the DOM, you get warnings about unknown attributes |
| Hidden coupling | If domain.js renames pricePerHour to price tomorrow, the component stops receiving it and doesn't fail: it just renders undefined |
| Blind refactors | Searching for "who passes the status prop" finds nothing, because nobody writes it |
The legitimate uses are wrapper components that forward props they don't know about:
// Our own button that adds styles and forwards everything else to the real <button>
function BrandButton({ children, ...rest }) {
return (
<button className="brand-button" {...rest}>
{children}
</button>
);
}
<BrandButton type="submit" disabled>Book</BrandButton>Here ...rest collects every prop we haven't destructured (type, disabled, onClick…) and forwards them. That's the case where the operator earns its keep.
Course rule: in CicloUrbano we'll pass the whole object as a named prop — bike={bike} — instead of spreading it. The contract stays explicit and the component's signature speaks for itself.
- Documenting a component's contract
Props are your component's public contract. In plain JavaScript, nothing stops you from passing a number where an object is expected, so it's worth writing it down.
Level 1: a comment at the top
Takes thirty seconds and solves 80% of the cases.
/**
* A card for one bike in CicloUrbano's catalogue.
*
* Props:
* - bike (object, required) { id, model, type, status, stationId, pricePerHour }
* - featured (boolean, optional, defaults to false) highlights the card
*/
function BikeCard({ bike, featured = false }) { … }Level 2: propTypes
Before TypeScript became widespread, type checking in React was done with the prop-types library, which validates at runtime, and only in development, printing a warning to the console:
import PropTypes from 'prop-types';
BikeCard.propTypes = {
bike: PropTypes.shape({
model: PropTypes.string.isRequired,
pricePerHour: PropTypes.number.isRequired
}).isRequired,
featured: PropTypes.bool
};We're mentioning it because you'll find it in a huge amount of existing code, but it's discouraged for new projects today: prop-types is an extra dependency, it only warns after the error has already happened, and it doesn't help your editor.
Level 3: TypeScript
This is the modern answer: types are checked before running anything, the editor autocompletes props, and renaming a field propagates on its own.
Properly typing components is the subject of TypeScript with React. Until then, in CicloUrbano we'll use level 1: a clear comment above every reusable component.
| Approach | When it checks | Cost | Recommendation |
|---|---|---|---|
| Comment | Never (a human reads it) | None | ✅ Bare minimum |
propTypes |
At runtime, only in development | One dependency | Only in projects that already use it |
| TypeScript | While writing and while compiling | Initial setup | ✅ Ideal for serious projects |
- CicloUrbano's catalogue, now parameterized
Time to break through the ceiling. Let's turn BikeCard and StatusBadge into parameterized components and paint different bikes.
StatusBadge with props
// src/components/StatusBadge.jsx
/**
* Visual badge for a bike's status.
* Props:
* - status (string, optional, defaults to 'disponible'):
* 'disponible' | 'alquilada' | 'mantenimiento'
*/
function StatusBadge({ status = 'disponible' }) {
return <span className={`status status--${status}`}>{status}</span>;
}
export default StatusBadge;The class gets built from a template: status--${status} produces status--disponible, status--alquilada, or status--mantenimiento, the CSS classes we defined in lesson 02-01. The entire "which color goes with which status" rule now lives in one single place.
BikeCard with props
// src/components/BikeCard.jsx
import StatusBadge from './StatusBadge.jsx';
/**
* A card for one bike in CicloUrbano's catalogue.
* Props:
* - bike (object, required) { id, model, type, status, stationId, pricePerHour }
* - stationName (string, optional, defaults to 'Unknown station')
*/
function BikeCard({ bike, stationName = 'Unknown station' }) {
const formattedPrice = bike.pricePerHour.toFixed(2);
return (
<article className={`bike-card bike-card--${bike.type}`}>
<h3>
{bike.model} <StatusBadge status={bike.status} />
</h3>
<p>Type: {bike.type}</p>
<p>Station: {stationName}</p>
<p>
<strong>€{formattedPrice} / hour</strong>
</p>
</article>
);
}
export default BikeCard;Here's what happened, point by point:
{ bike, stationName = '…' }: destructuring in the signature, with a default value for the optional prop.formattedPrice: a variable derived from the prop. We read the prop and compute; we do not modify it.toFixed(2)gives us"2.50", already in the decimal-point format we want.bike-card--${bike.type}: the variant class is no longer hand-written; it comes from the data.urbana,electrica, orcarga.<StatusBadge status={bike.status} />:BikeCardisStatusBadge's parent and passes it a prop. Props flow down the tree level by level.
BikeList distributes the data
// src/components/BikeList.jsx
import BikeCard from './BikeCard.jsx';
/**
* Catalogue section. Receives the first three bikes via props.
* (In 03-03 it'll switch to receiving the full array and looping with map.)
*/
function BikeList({ first, second, third }) {
return (
<section className="bike-list">
<h2>Available bikes</h2>
<BikeCard bike={first} stationName="Main Square" />
<BikeCard bike={second} stationName="Main Square" />
<BikeCard bike={third} stationName="North Park" />
</section>
);
}
export default BikeList;Yes, three numbered props is an ugly solution, and it's ugly on purpose: it's the symptom of a missing tool. That tool is looping over an array with map, and it arrives in Lists and Keys. Notice that the problem is no longer about props — it's about repetition.
App wires up the real data
// src/App.jsx
import { bikes } from './data/domain.js';
import Header from './components/Header.jsx';
import BikeList from './components/BikeList.jsx';
import Footer from './components/Footer.jsx';
function App() {
return (
<>
<Header />
<main>
<BikeList
first={bikes[0]}
second={bikes[1]}
third={bikes[2]}
/>
</main>
<Footer />
</>
);
}
export default App;Save and check the browser. Finally:
| Card | Model | Badge | Variant border |
|---|---|---|---|
| 1 | Classic Urban | green disponible |
green (urbana) |
| 2 | Electric Pro | orange alquilada |
orange (electrica) |
| 3 | Cargo Max | red mantenimiento |
red (carga) |
Three different cards, produced by a single component. That's the moment React starts paying off: any design change to the card gets written once and applies to all three.
The props tree
flowchart TD
DOM["domain.js<br/>bikes[]"] --> APP[App]
APP -- "first / second / third" --> LIS[BikeList]
LIS -- "bike, stationName" --> T1["BikeCard<br/>bici-001"]
LIS -- "bike, stationName" --> T2["BikeCard<br/>bici-002"]
LIS -- "bike, stationName" --> T3["BikeCard<br/>bici-003"]
T1 -- "status" --> E1[StatusBadge]
T2 -- "status" --> E2[StatusBadge]
T3 -- "status" --> E3[StatusBadge]
Every arrow points down. None point up. That's React's one-way data flow.
Common Mistakes and Tips
- Passing numbers as strings.
pricePerHour="2.5"delivers the text"2.5", and any arithmetic operation ortoFixedwill fail. If it's not literal text, it goes in braces. - Forgetting the destructuring braces.
function BikeCard(bike)receives the entirepropsobject under the namebike, sobike.modelisundefined. The correct signature isfunction BikeCard({ bike }). - Mutating an object-type prop.
bike.status = 'alquilada'doesn't throw an error, which is exactly why it's so dangerous: you modify the parent's data without React ever knowing. If the data needs to change, it's state. - Executing the function when passing it.
onBook={book()}callsbookduring render and passes its result. No parentheses:onBook={book}. - Relying on the default value against a
null. It only applies withundefined. If the parent can sendnull, handle it explicitly. - Overusing
{...object}. Convenient to write, expensive to maintain. Save it for wrappers that forward unknown props. - Using
defaultPropsin a function component. Removed in React 19. Use default parameters. - Tip: the signature is documentation.
function BikeCard({ bike, featured = false })says more than any comment. Take care with prop names: they're your component's public vocabulary. - Tip: if a component takes more than five or six props, be suspicious. Either it's doing too much, or several props should travel grouped in an object.
- Tip: inspect props in DevTools. The Components tab shows every instance's real props. It's the fastest way to discover you're passing
undefined.
Exercises
Exercise 1
Turn StationCard (created in lesson 01-03 with hard-coded data) into a parameterized component. It should receive a station prop shaped like domain.js, and an optional bikesAtStation prop (number, defaults to 0), and show the name, the district, the total docks, and the number of bikes. Then use it in App.jsx to paint CicloUrbano's three stations, writing them out one by one.
Exercise 2
Create the Panel component described in section 6 (src/components/Panel.jsx), with a title prop (string, required) and children. Add an optional footer prop that accepts a JSX element and gets painted below the content when provided. Then use it twice in App.jsx: once wrapping the bike list, and once with a text notice.
Hint: to only paint the footer when it exists, remember that {undefined} paints nothing in JSX.
Exercise 3
This component has four props-related bugs. Find them, explain what each one causes, and write the corrected version.
function BikeRow(bike, pricePerHour = 0) {
bike.model = bike.model.toUpperCase();
return (
<tr>
<td>{bike.model}</td>
<td>€{pricePerHour.toFixed(2)}</td>
</tr>
);
}
// Usage:
<BikeRow bike={bikes[0]} pricePerHour="2.5" />Solutions
Solution 1.
// src/components/StationCard.jsx
/**
* A card for one CicloUrbano station.
* Props:
* - station (object, required) { id, name, district, docks }
* - bikesAtStation (number, optional, defaults to 0)
*/
function StationCard({ station, bikesAtStation = 0 }) {
const freeDocks = station.docks - bikesAtStation;
return (
<article className="station-card">
<h3>{station.name}</h3>
<p>District: {station.district}</p>
<p>Docks: {station.docks}</p>
<p>
Bikes: {bikesAtStation} · Free: {freeDocks}
</p>
</article>
);
}
export default StationCard;// src/App.jsx (excerpt)
import { stations } from './data/domain.js';
import StationCard from './components/StationCard.jsx';
<section>
<h2>Stations</h2>
<StationCard station={stations[0]} bikesAtStation={2} />
<StationCard station={stations[1]} bikesAtStation={2} />
<StationCard station={stations[2]} bikesAtStation={1} />
</section>Notice freeDocks: it's a value derived from the props, computed on every render. It's not a prop, and there's no need to store it anywhere. That distinction between what gets stored and what gets computed will be central to the next lesson.
Solution 2.
// src/components/Panel.jsx
/**
* Container with a title and free-form content.
* Props:
* - title (string, required)
* - children (content, required)
* - footer (JSX element, optional)
*/
function Panel({ title, children, footer }) {
return (
<section className="panel">
<h2 className="panel__title">{title}</h2>
<div className="panel__body">{children}</div>
{footer && <div className="panel__footer">{footer}</div>}
</section>
);
}
export default Panel;// src/App.jsx (excerpt)
<Panel title="Catalogue" footer={<small>Prices include tax.</small>}>
<BikeList
first={bikes[0]}
second={bikes[1]}
third={bikes[2]}
/>
</Panel>
<Panel title="Notice">
<p>The cargo bike service is under maintenance until Monday.</p>
</Panel>The same Panel wraps a whole list in one case and a paragraph in the other: that's what children brings to the table. The expression {footer && <div…>} is conditional rendering, covered in depth in Conditional Rendering; here it's enough to know that if footer is undefined, nothing gets painted.
Solution 3.
The four bugs:
- Wrong signature.
function BikeRow(bike, pricePerHour = 0)treats the props as two separate parameters. React always passes a single object as the first argument, sobikeends up being the entirepropsobject andpricePerHournever receives anything: it stays at its default,0. You need to destructure:({ bike, pricePerHour = 0 }). - Mutating a prop.
bike.model = bike.model.toUpperCase()modifies the object fromdomain.js, affecting the whole application without triggering any render. You need to compute a new variable instead. - A number passed as a string.
pricePerHour="2.5"delivers the text"2.5";"2.5".toFixeddoesn't exist and throws aTypeError. It should bepricePerHour={2.5}. - Inconsistent contract.
pricePerHouralready lives inside thebikeobject; passing it separately as well duplicates the source of truth and lets the two contradict each other. It should be read from the bike.
Corrected version:
// src/components/BikeRow.jsx
/**
* A row for one bike, for table views.
* Props:
* - bike (object, required) { id, model, type, status, stationId, pricePerHour }
*/
function BikeRow({ bike }) {
const upperCaseModel = bike.model.toUpperCase();
const formattedPrice = bike.pricePerHour.toFixed(2);
return (
<tr>
<td>{upperCaseModel}</td>
<td>€{formattedPrice}</td>
</tr>
);
}
export default BikeRow;Conclusion
Props are the mechanism a component uses to receive data from outside, and with them CicloUrbano's catalogue has stopped being a mockup: a single BikeCard now paints three different bikes, each with its own model, price, color variant, and status badge. You know how to pass them as attributes — quoted text, everything else in braces — receive them with destructuring in the signature, give them default values with default parameters (and that defaultProps no longer exists for functions in React 19), pass any type of value — including objects, functions, and JSX elements — and use the special children prop to build containers like Panel that wrap arbitrary content.
And you have the rule that holds the whole model together crystal clear: props are read-only. A component reads them and derives values from them, but never writes to them. Data flow goes down the tree and never up, which means tracing the origin of any piece of data is always possible. You also know that spreading props with {...object} is convenient but erases the contract, and that the contract is worth documenting — today with a comment, and in the future with TypeScript.
There's one big open question left: if props come from outside and can't be modified, how does anything ever change in a React application? When the user picks a filter, books a bike, or clicks a button, something has to change and trigger a new render. That something is state: a component's own memory, the data that can change and that, when it does, triggers the render cycle you studied in 01-05. That's the subject of the next lesson: State: Managing Component State.
React Course
Module 1: Getting Started with React
- What Is React?
- Setting Up the Development Environment
- Hello World in React
- JSX: A JavaScript Syntax Extension
- How React Renders: Virtual DOM and Reconciliation
Module 2: React Components
- Understanding Components
- Function vs Class Components
- Props: Passing Data to Components
- State: Managing Component State
- Styling Components: CSS, Modules and Utilities
Module 3: Working with Events
- Handling Events in React
- Conditional Rendering
- Lists and Keys
- Forms and Controlled Components
- Form Validation and Uncontrolled Components
- Accessibility in Interactive Components
Module 4: Advanced Component Concepts
- Lifting State Up
- Composition vs Inheritance
- React Lifecycle Methods
- Hooks: Introduction and Basic Use
- Error Boundaries: Catching Failures in the UI
Module 5: React Hooks
- The useState Hook
- The useEffect Hook
- The useRef Hook and DOM Access
- The useContext Hook
- The useReducer Hook
- Custom Hooks
Module 6: Routing in React
- Introducing React Router
- Setting Up React Router
- Nested Routes
- Programmatic Navigation
- Protected Routes and Access Control
Module 7: State Management
- Introduction to State Management
- The Context API
- Redux: Introduction and Setup
- Redux: Actions and Reducers
- Redux: Connecting to React
- Server State: Fetching, Caching and Syncing
Module 8: Performance Optimization
- Performance Optimization Techniques in React
- Memoization with React.memo
- The useMemo and useCallback Hooks
- Code Splitting and Lazy Loading
- Measuring Performance with React DevTools Profiler
Module 9: Testing React Applications
- Introduction to Testing
- Unit Testing with Jest
- Component Testing with React Testing Library
- Testing Asynchronous Code and Mocking APIs
- End-to-End Testing with Cypress
Module 10: Advanced Topics
- Server-Side Rendering (SSR) with Next.js
- Static Site Generation (SSG) with Next.js
- Suspense and React Server Components
- TypeScript with React
- React Native: Building Mobile Apps
