Learn React in 10 DaysDay 3: Components and Props

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>&copy; 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

  1. Component names must start with uppercase
  2. Props are read-only (don't modify them in child components)
  3. children enables flexible components
  4. Keep components small for better maintainability
  5. 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.