Setting up a React project with Vite
The mental model: how React renders (2)
Props — short for properties — are how you pass data from a parent component to a child. They work like HTML attributes, except the names are yours to define and the values can be any JavaScript type.
Props are what make components reusable. The same <UserCard /> can render hundreds of different users by receiving different data each time.
When you write <UserCard name="Wouter" age={32} />, React calls the UserCard function with a single argument: an object containing all the props you passed.
// React calls it like this internally:
UserCard({ name: 'Wouter', age: 32 });
You can receive that object as props and access values from it:
function UserCard(props) {
return <h3>{props.name} is {props.age}</h3>;
}
In practice, you'll always destructure in the function signature:
function UserCard({ name, age }) {
return <h3>{name} is {age}</h3>;
}
Same result, less noise. Destructuring is what you'll see in virtually every real codebase.
Strings can use quotes. Everything else uses curly braces.
<UserCard
name="Wouter" // string — quotes
age={32} // number — braces
isOnline={true} // boolean — braces
hobbies={['chess', 'ramen']} // array — braces
address={{ city: 'Amsterdam' }} // object — braces (note the double {{}})
onClick={handleClick} // function — braces
/>
When in doubt, use {}. The only thing quotes are shorthand for is strings.
<aside> ⚠️
Warning
A few mistakes you'll make at least once:
<Card title=42 /> — that 42 becomes the string "42". Use title={42}.<Card style="color: red" /> — this crashes. style must be an object: style={{ color: 'red' }}.function Card(name) — name here is the entire props object, not the name value. Always destructure: function Card({ name }).
</aside>You can give props default values directly in the destructuring:
function Button({ label = 'Click me', color = 'blue' }) {
return <button style={{ color }}>{label}</button>;
}
<Button /> // "Click me", blue
<Button label="Save" /> // "Save", blue
<Button label="Delete" color="red" /> // "Delete", red
<aside> 💡
Info
Default values only apply when the prop is undefined — when the parent didn't pass it at all. Passing label={undefined} triggers the default. Passing label={null} does not.
</aside>
Anything between a component's opening and closing tags is automatically passed as a special prop called children:
function Card({ children }) {
return <div className="card">{children}</div>;
}
<Card>
<h2>Hello</h2>
<p>This content is passed as children.</p>
</Card>
The <h2> and <p> become children. This is the pattern behind layout components — cards, modals, page wrappers, anything that needs to wrap arbitrary content without caring what's inside.
<aside> ⌨️
Hands On
Build a Section component that renders a <section> with a heading and whatever children are passed in:
<Section title="Featured Products">
<p>Some content here</p>
</Section>
The title should render as an <h2> inside the section. The children should render below it.
</aside>
function UserCard({ name, role, isOnline, avatarUrl }) {
return (
<div className="user-card">
<img src={avatarUrl} alt={name} width={48} />
<div>
<h3>{name}</h3>
<p>Role: {role}</p>
<p>Status: {isOnline ? '🟢 Online' : '⚪ Offline'}</p>
</div>
</div>
);
}
function App() {
return (
<div>
<h1>Team</h1>
<UserCard
name="Wouter"
role="Mentor"
isOnline={true}
avatarUrl="<https://i.pravatar.cc/48?u=wouter>"
/>
<UserCard
name="Noer"
role="Trainee"
isOnline={false}
avatarUrl="<https://i.pravatar.cc/48?u=noer>"
/>
<UserCard
name="Federico"
role="Mentor"
isOnline={true}
avatarUrl="<https://i.pravatar.cc/48?u=federico>"
/>
</div>
);
}
One component definition. Three different users. That's props doing their job.
When your data already lives in an object, you can spread it directly onto a component instead of listing each prop manually:
const user = { name: 'Wouter', role: 'Mentor', isOnline: true, avatarUrl: '...' };
<UserCard {...user} />
This becomes especially useful when you're mapping over an array of objects — which is exactly what you'll do in the next lesson.
TypeScript lets you define exactly which props a component expects and what types they should be. You do this with an interface:
interface UserCardProps {
name: string;
role: string;
isOnline: boolean;
avatarUrl: string;
}
function UserCard({ name, role, isOnline, avatarUrl }: UserCardProps) {
return (
<div className="user-card">
<img src={avatarUrl} alt={name} width={48} />
<div>
<h3>{name}</h3>
<p>Role: {role}</p>
<p>Status: {isOnline ? '🟢 Online' : '⚪ Offline'}</p>
</div>
</div>
);
}
Now if you forget a required prop or pass the wrong type, TypeScript tells you immediately in your editor — before you run anything.
For optional props, use ?:
interface ButtonProps {
label: string;
color?: string; // optional — has a default value
}
<aside> 💡
Info
Naming the interface after the component with Props at the end (UserCardProps, ButtonProps) is the standard convention. You'll see it everywhere in TypeScript React codebases.
</aside>
Props flow one way: from parent to child. A component cannot modify the props it receives.
function Bad({ count }) {
count = count + 1; // ❌ Don't do this
return <p>{count}</p>;
}
If you need data to change over time, that's what state is for. You'll meet useState in week 8 and it will make this constraint click into place.
<aside> 🎉
Celebration
Props are the backbone of component communication in React. You now know how to make components reusable, how to type them with TypeScript, and how the children pattern works. That's most of what you'll use props for across the entire course.
</aside>
defaultProps are not relevant anymore in 2026The HackYourFuture curriculum is licensed under CC BY-NC-SA 4.0 *https://hackyourfuture.net/*

Built with ❤️ by the HackYourFuture community · Thank you, contributors
Found a mistake or have a suggestion? Let us know in the feedback form.