Day 3: Components and Props
What You'll Learn Today
- How to create and structure components
- Passing data with Props
- Using the children property
- Guidelines for component splitting
- Setting default Props
What Are Components?
Components are independent, reusable pieces that make up your UI. React applications are built as a tree of components.
flowchart TB
subgraph Tree["Component Tree"]
App["App"]
Header["Header"]
Main["Main"]
Footer["Footer"]
Nav["Nav"]
Logo["Logo"]
Article["Article"]
Sidebar["Sidebar"]
end
App --> Header
App --> Main
App --> Footer
Header --> Logo
Header --> Nav
Main --> Article
Main --> Sidebar
style App fill:#3b82f6,color:#fff
style Header fill:#8b5cf6,color:#fff
style Main fill:#8b5cf6,color:#fff
style Footer fill:#8b5cf6,color:#fff
Creating Components
In React, you create components using functions.
// A simple component
function Welcome() {
return <h1>Welcome!</h1>;
}
// Using the component
function App() {
return (
<div>
<Welcome />
<Welcome />
<Welcome />
</div>
);
}
TypeScript version
// A simple component
function Welcome() {
return <h1>Welcome!</h1>;
}
// Using the component
function App() {
return (
<div>
<Welcome />
<Welcome />
<Welcome />
</div>
);
}
Component Naming Conventions
| Rule | Description |
|---|---|
| Start with uppercase | Welcome, UserCard (lowercase is interpreted as HTML tags) |
| PascalCase | Multiple words like UserProfileCard |
| Meaningful names | Names should describe the function |
What Are Props?
Props (Properties) are the mechanism for passing data from parent to child components.
flowchart LR
subgraph Flow["Props Flow"]
Parent["Parent Component<br/>App"]
Child["Child Component<br/>Greeting"]
end
Parent -->|"name='Alice'"| Child
style Parent fill:#3b82f6,color:#fff
style Child fill:#22c55e,color:#fff
Basic Props Usage
// Child component - receives props
function Greeting(props) {
return <h1>Hello, {props.name}!</h1>;
}
// Parent component - passes props
function App() {
return (
<div>
<Greeting name="Alice" />
<Greeting name="Bob" />
<Greeting name="Carol" />
</div>
);
}
TypeScript version
// Child component - receives props
interface GreetingProps {
name: string;
}
function Greeting(props: GreetingProps) {
return <h1>Hello, {props.name}!</h1>;
}
// Parent component - passes props
function App() {
return (
<div>
<Greeting name="Alice" />
<Greeting name="Bob" />
<Greeting name="Carol" />
</div>
);
}
Destructuring Props
Use destructuring for cleaner code.
// Using destructuring
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Receiving multiple props
function UserCard({ name, age, email }) {
return (
<div className="user-card">
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
);
}
// Usage
<UserCard name="Alice" age={25} email="alice@example.com" />
TypeScript version
// Using destructuring
interface GreetingProps {
name: string;
}
function Greeting({ name }: GreetingProps) {
return <h1>Hello, {name}!</h1>;
}
// Receiving multiple props
interface UserCardProps {
name: string;
age: number;
email: string;
}
function UserCard({ name, age, email }: UserCardProps) {
return (
<div className="user-card">
<h2>{name}</h2>
<p>Age: {age}</p>
<p>Email: {email}</p>
</div>
);
}
// Usage
<UserCard name="Alice" age={25} email="alice@example.com" />
Props Types
Props can accept various types of values.
function Example({
text, // string
count, // number
isActive, // boolean
items, // array
user, // object
onClick, // function
}) {
return (
<div>
<p>{text}</p>
<p>Count: {count}</p>
<p>Status: {isActive ? 'Active' : 'Inactive'}</p>
<ul>
{items.map((item, i) => <li key={i}>{item}</li>)}
</ul>
<p>User: {user.name}</p>
<button onClick={onClick}>Click</button>
</div>
);
}
// Usage
<Example
text="Hello"
count={42}
isActive={true}
items={['A', 'B', 'C']}
user={{ name: 'Alice', age: 25 }}
onClick={() => alert('Clicked!')}
/>
TypeScript version
interface User {
name: string;
}
interface ExampleProps {
text: string;
count: number;
isActive: boolean;
items: string[];
user: User;
onClick: () => void;
}
function Example({
text,
count,
isActive,
items,
user,
onClick,
}: ExampleProps) {
return (
<div>
<p>{text}</p>
<p>Count: {count}</p>
<p>Status: {isActive ? 'Active' : 'Inactive'}</p>
<ul>
{items.map((item, i) => <li key={i}>{item}</li>)}
</ul>
<p>User: {user.name}</p>
<button onClick={onClick}>Click</button>
</div>
);
}
// Usage
<Example
text="Hello"
count={42}
isActive={true}
items={['A', 'B', 'C']}
user={{ name: 'Alice', age: 25 }}
onClick={() => alert('Clicked!')}
/>
The children Property
children is a special prop that receives content placed between a component's opening and closing tags.
// Card component
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
// Usage
function App() {
return (
<Card>
<h2>Title</h2>
<p>This is the card content.</p>
</Card>
);
}
TypeScript version
import { ReactNode } from 'react';
// Card component
interface CardProps {
children: ReactNode;
}
function Card({ children }: CardProps) {
return (
<div className="card">
{children}
</div>
);
}
// Usage
function App() {
return (
<Card>
<h2>Title</h2>
<p>This is the card content.</p>
</Card>
);
}
flowchart TB
subgraph Children["How children Works"]
Card["Card<br/>className='card'"]
Content["children<br/>(h2 + p)"]
end
Card --> Content
style Card fill:#3b82f6,color:#fff
style Content fill:#22c55e,color:#fff
children Use Cases
// Layout component
function PageLayout({ children }) {
return (
<div className="page">
<header>Header</header>
<main>{children}</main>
<footer>Footer</footer>
</div>
);
}
// Button component
function Button({ children, onClick }) {
return (
<button className="btn" onClick={onClick}>
{children}
</button>
);
}
// Usage
<PageLayout>
<h1>Welcome</h1>
<Button onClick={() => alert('Click')}>
Learn More
</Button>
</PageLayout>
TypeScript version
import { ReactNode } from 'react';
// Layout component
interface PageLayoutProps {
children: ReactNode;
}
function PageLayout({ children }: PageLayoutProps) {
return (
<div className="page">
<header>Header</header>
<main>{children}</main>
<footer>Footer</footer>
</div>
);
}
// Button component
interface ButtonProps {
children: ReactNode;
onClick: () => void;
}
function Button({ children, onClick }: ButtonProps) {
return (
<button className="btn" onClick={onClick}>
{children}
</button>
);
}
// Usage
<PageLayout>
<h1>Welcome</h1>
<Button onClick={() => alert('Click')}>
Learn More
</Button>
</PageLayout>
Component Composition
Build complex UIs by combining small components.
// Avatar component
function Avatar({ src, alt }) {
return <img className="avatar" src={src} alt={alt} />;
}
// User info component
function UserInfo({ name, title }) {
return (
<div className="user-info">
<p className="name">{name}</p>
<p className="title">{title}</p>
</div>
);
}
// Comment component (composed)
function Comment({ author, text, date }) {
return (
<div className="comment">
<Avatar src={author.avatarUrl} alt={author.name} />
<UserInfo name={author.name} title={author.title} />
<p className="comment-text">{text}</p>
<p className="comment-date">{date}</p>
</div>
);
}
TypeScript version
// Avatar component
interface AvatarProps {
src: string;
alt: string;
}
function Avatar({ src, alt }: AvatarProps) {
return <img className="avatar" src={src} alt={alt} />;
}
// User info component
interface UserInfoProps {
name: string;
title: string;
}
function UserInfo({ name, title }: UserInfoProps) {
return (
<div className="user-info">
<p className="name">{name}</p>
<p className="title">{title}</p>
</div>
);
}
// Comment component (composed)
interface Author {
avatarUrl: string;
name: string;
title: string;
}
interface CommentProps {
author: Author;
text: string;
date: string;
}
function Comment({ author, text, date }: CommentProps) {
return (
<div className="comment">
<Avatar src={author.avatarUrl} alt={author.name} />
<UserInfo name={author.name} title={author.title} />
<p className="comment-text">{text}</p>
<p className="comment-date">{date}</p>
</div>
);
}
flowchart TB
subgraph Composition["Component Composition"]
Comment["Comment"]
Avatar["Avatar"]
UserInfo["UserInfo"]
Text["Comment Text"]
Date["Date"]
end
Comment --> Avatar
Comment --> UserInfo
Comment --> Text
Comment --> Date
style Comment fill:#3b82f6,color:#fff
style Avatar fill:#22c55e,color:#fff
style UserInfo fill:#22c55e,color:#fff
Guidelines for Splitting Components
When to Split Components
| Indicator | Description |
|---|---|
| Reusability | Same UI used in multiple places |
| Complexity | Component is getting too large |
| Separation of concerns | Different concerns are mixed together |
| Testability | Parts that need independent testing |
Example: Before and After Splitting
// β Before: Too much in one component
function ProductPage() {
return (
<div>
<header>
<img src="/logo.png" alt="Logo" />
<nav>
<a href="/">Home</a>
<a href="/products">Products</a>
</nav>
</header>
<main>
<div className="product">
<img src="/product.jpg" alt="Product" />
<h1>Product Name</h1>
<p>$100</p>
<button>Add to Cart</button>
</div>
<div className="reviews">
<h2>Reviews</h2>
{/* Review list */}
</div>
</main>
<footer>Β© 2024</footer>
</div>
);
}
// β
After: Components separated by responsibility
function Logo() {
return <img src="/logo.png" alt="Logo" />;
}
function Navigation() {
return (
<nav>
<a href="/">Home</a>
<a href="/products">Products</a>
</nav>
);
}
function Header() {
return (
<header>
<Logo />
<Navigation />
</header>
);
}
function ProductDetail({ product }) {
return (
<div className="product">
<img src={product.image} alt={product.name} />
<h1>{product.name}</h1>
<p>${product.price}</p>
<button>Add to Cart</button>
</div>
);
}
function ReviewList({ reviews }) {
return (
<div className="reviews">
<h2>Reviews</h2>
{reviews.map(review => (
<ReviewItem key={review.id} review={review} />
))}
</div>
);
}
function Footer() {
return <footer>Β© 2024</footer>;
}
function ProductPage({ product, reviews }) {
return (
<div>
<Header />
<main>
<ProductDetail product={product} />
<ReviewList reviews={reviews} />
</main>
<Footer />
</div>
);
}
TypeScript version
function Logo() {
return <img src="/logo.png" alt="Logo" />;
}
function Navigation() {
return (
<nav>
<a href="/">Home</a>
<a href="/products">Products</a>
</nav>
);
}
function Header() {
return (
<header>
<Logo />
<Navigation />
</header>
);
}
interface Product {
image: string;
name: string;
price: number;
}
interface ProductDetailProps {
product: Product;
}
function ProductDetail({ product }: ProductDetailProps) {
return (
<div className="product">
<img src={product.image} alt={product.name} />
<h1>{product.name}</h1>
<p>${product.price}</p>
<button>Add to Cart</button>
</div>
);
}
interface Review {
id: number;
text: string;
}
interface ReviewListProps {
reviews: Review[];
}
function ReviewList({ reviews }: ReviewListProps) {
return (
<div className="reviews">
<h2>Reviews</h2>
{reviews.map(review => (
<ReviewItem key={review.id} review={review} />
))}
</div>
);
}
function Footer() {
return <footer>© 2024</footer>;
}
interface ProductPageProps {
product: Product;
reviews: Review[];
}
function ProductPage({ product, reviews }: ProductPageProps) {
return (
<div>
<Header />
<main>
<ProductDetail product={product} />
<ReviewList reviews={reviews} />
</main>
<Footer />
</div>
);
}
Default Props
You can set default values for props.
// Method 1: Default parameters (recommended)
function Button({ text = 'Click', color = 'blue' }) {
return (
<button style={{ backgroundColor: color }}>
{text}
</button>
);
}
// Usage
<Button /> // Uses defaults
<Button text="Submit" /> // Overrides text only
<Button color="red" /> // Overrides color only
<Button text="Delete" color="red" /> // Overrides both
TypeScript version
// Method 1: Default parameters (recommended)
interface ButtonProps {
text?: string;
color?: string;
}
function Button({ text = 'Click', color = 'blue' }: ButtonProps) {
return (
<button style={{ backgroundColor: color }}>
{text}
</button>
);
}
// Usage
<Button /> // Uses defaults
<Button text="Submit" /> // Overrides text only
<Button color="red" /> // Overrides color only
<Button text="Delete" color="red" /> // Overrides both
// Method 2: OR operator
function Greeting({ name }) {
return <h1>Hello, {name || 'Guest'}!</h1>;
}
// Method 3: Nullish coalescing operator
function Counter({ count }) {
return <p>Count: {count ?? 0}</p>;
}
Choosing Default Value Methods
| Method | When to Use |
|---|---|
| Default parameters | Use this as the default approach |
OR operator || |
When falsy values (0, '') should also use default |
Nullish coalescing ?? |
When only null/undefined should use default |
Spreading Props
You can pass all properties of an object as props at once.
function UserProfile({ name, age, email, avatar }) {
return (
<div>
<img src={avatar} alt={name} />
<h2>{name}</h2>
<p>{age} years old</p>
<p>{email}</p>
</div>
);
}
// Normal way
const user = { name: 'Alice', age: 25, email: 'alice@example.com', avatar: '/alice.jpg' };
<UserProfile
name={user.name}
age={user.age}
email={user.email}
avatar={user.avatar}
/>
// Using spread syntax
<UserProfile {...user} />
TypeScript version
interface UserProfileProps {
name: string;
age: number;
email: string;
avatar: string;
}
function UserProfile({ name, age, email, avatar }: UserProfileProps) {
return (
<div>
<img src={avatar} alt={name} />
<h2>{name}</h2>
<p>{age} years old</p>
<p>{email}</p>
</div>
);
}
// Normal way
const user: UserProfileProps = { name: 'Alice', age: 25, email: 'alice@example.com', avatar: '/alice.jpg' };
<UserProfile
name={user.name}
age={user.age}
email={user.email}
avatar={user.avatar}
/>
// Using spread syntax
<UserProfile {...user} />
Extracting Some Props
function Button({ children, className, ...rest }) {
return (
<button className={`btn ${className}`} {...rest}>
{children}
</button>
);
}
// Usage
<Button className="primary" onClick={handleClick} disabled={true}>
Submit
</Button>
TypeScript version
import { ReactNode, ComponentPropsWithoutRef } from 'react';
interface ButtonProps extends ComponentPropsWithoutRef<'button'> {
children: ReactNode;
className?: string;
}
function Button({ children, className, ...rest }: ButtonProps) {
return (
<button className={`btn ${className}`} {...rest}>
{children}
</button>
);
}
// Usage
<Button className="primary" onClick={handleClick} disabled={true}>
Submit
</Button>
Summary
| Concept | Description |
|---|---|
| Component | Independent, reusable UI pieces |
| Props | Mechanism for passing data from parent to child |
| children | Special prop that receives content between tags |
| Component composition | Building UI by combining small components |
| Default Props | Setting initial values for props |
Key Takeaways
- Component names must start with uppercase
- Props are read-only (don't modify them in child components)
childrenenables flexible components- Keep components small for better maintainability
- Default parameters set initial prop values
Exercises
Exercise 1: Basics
Create a ProfileCard component that receives these props:
- name
- job
- bio (biography)
Exercise 2: children
Create a Panel component that receives a title and children, displaying them in a styled panel.
<Panel title="Notice">
<p>We're closed tomorrow.</p>
</Panel>
Challenge
Create a ProductCard component that meets these requirements:
- Receives product name, price, image URL, and stock count
- Shows "Sold Out" badge when out of stock
- Shows "Premium" badge when price is over $100
References
Coming Up Next: On Day 4, we'll learn about "State and Events." Create interactive UIs that respond to user actions.