Skip to main content

🧩 Components and Props

Components are the LEGO bricks of React, and props are how you configure each brick without rewriting it. Once you can build a component and feed it data, you can compose an entire interface from small, reusable pieces — the same Button or Card used a dozen ways across your app. This lesson is where React stops being theory and starts being a way of building.

Week 4 · Monday: React Fundamentals · Lecture 3

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Write function components and use them like custom HTML tags
  • Pass data into components with props and read them via destructuring
  • Provide default values for props that may be omitted
  • Explain why props are read-only and how one-way data flow works
  • Use the special children prop to compose components
  • Send data back up to a parent with callback props

Estimated Time: 65 minutes

Practice: Build a reusable Card with children and a ProductCard that reports clicks upward.

In This Lesson

What Are Components?

A component is an independent, reusable piece of UI — and in modern React it's simply a JavaScript function that returns JSX. Building a house, you don't carve every door and window from raw wood; you install pre-made, standardized parts. React works the same way: you assemble an interface out of components, and components out of smaller components.

Two rules make a function a valid component: it returns JSX, and its name is capitalized (so React tells it apart from a plain <div>).

// The three ways to write the same function component
function Welcome(props) {
    return <h1>Hello, {props.name}!</h1>;
}

const Welcome = (props) => {
    return <h1>Hello, {props.name}!</h1>;
};

const Welcome = (props) => <h1>Hello, {props.name}!</h1>;   // implicit return

// Use it like a custom tag:
<Welcome name="Sarah" />

📖 Function components only

You may see older tutorials define components with class ... extends React.Component. Modern React is written entirely with function components and hooks — they're shorter, easier to reason about, and the approach the official docs teach. Recognize the class syntax in legacy code, but write functions.

Components nest into a tree. Your whole app is one top-level component (often App) containing others, containing others still:

graph TD A[App] --> B[Header] A --> C[Main] A --> D[Footer] B --> E[Logo] B --> F[Navigation] C --> G[Article] C --> H[Sidebar] G --> I[Title] G --> J[Content] G --> K[Author]

Passing Data with Props

Props (short for "properties") are how a parent hands data to a child — exactly like passing arguments to a function. You set them as attributes in JSX, and the component receives them as a single props object.

// Parent passes data down as props
function App() {
    return (
        <UserCard
            name="John Doe"
            email="john@example.com"
            age={30}
            isActive={true}
        />
    );
}

// Child receives them on the props object
function UserCard(props) {
    return (
        <div className="user-card">
            <h2>{props.name}</h2>
            <p>Email: {props.email}</p>
            <p>Age: {props.age}</p>
            <p>Status: {props.isActive ? 'Active' : 'Inactive'}</p>
        </div>
    );
}

💡 Strings vs. everything else

String props use quotes: name="John". Every other type — numbers, booleans, arrays, objects, functions — goes inside curly braces: age={30}, isActive={true}, items={[1, 2, 3]}. A lone boolean like disabled is shorthand for disabled={true}.

Destructuring & Defaults

Typing props. in front of every value gets noisy. Destructure the props right in the function's parameter list — it reads far more cleanly and documents exactly what the component expects.

// Without destructuring
function UserCard(props) {
    return <h2>{props.name}</h2>;
}

// ✅ With destructuring — clearer, self-documenting
function UserCard({ name, email }) {
    return (
        <div>
            <h2>{name}</h2>
            <p>{email}</p>
        </div>
    );
}

Default values

Give props defaults directly in the destructuring so a missing prop doesn't break the component. This is the modern replacement for the older Component.defaultProps.

function Button({ text = 'Click me', color = 'blue', size = 'medium' }) {
    return (
        <button className={`btn btn-${color} btn-${size}`}>
            {text}
        </button>
    );
}

<Button />                              {/* "Click me", blue, medium */}
<Button text="Save" color="green" />    {/* overrides two, keeps size default */}

Props Are Read-Only

A component must never modify its own props. Think of props as ingredients handed to a recipe: you cook with them, but you don't reach back and alter the pantry. React relies on this to keep data flow predictable.

// ❌ WRONG — mutating props
function Wrong(props) {
    props.name = 'New Name';        // don't do this
    return <h1>{props.name}</h1>;
}

// ✅ CORRECT — derive a new value, leave props untouched
function Right({ name }) {
    const displayName = name.toUpperCase();
    return <h1>{displayName}</h1>;
}
Props flow down from parent to child; events flow back up via callbacks Parent Child A Child B props ↓ props ↓ events ↑
One-way data flow: data travels down through props; children report back up through callback props.

The children Prop

Whatever you nest between a component's opening and closing tags arrives as a special prop called children. This lets you build wrapper components — cards, layouts, dialogs — that don't need to know their contents in advance, like a picture frame that holds any photo.

// A reusable wrapper that renders whatever it's given
function Card({ title, children }) {
    return (
        <div className="card">
            <div className="card-header"><h3>{title}</h3></div>
            <div className="card-body">{children}</div>
        </div>
    );
}

// Anything nested inside becomes `children`
function App() {
    return (
        <Card title="User Profile">
            <p>Name: John Doe</p>
            <p>Email: john@example.com</p>
            <button>Edit Profile</button>
        </Card>
    );
}

✅ Why this is powerful

One Card component now wraps any content — a profile, a chart, a form. You write the frame once and reuse it everywhere, keeping styling and structure consistent across the whole app.

Callbacks: Data Up

Props flow down — but how does a child tell its parent that something happened, like a button being clicked? The parent passes a function as a prop, and the child calls it. This is how React keeps state in one place while children stay reusable.

// Parent owns the state and passes a callback down
function ShoppingCart() {
    const [items, setItems] = useState([]);

    function addItem(product) {
        setItems([...items, product]);          // parent updates its own state
    }

    return (
        <div>
            {/* Child calls onAddToCart when its button is clicked */}
            <ProductCard product={{ id: 1, name: 'Coffee mug' }} onAddToCart={addItem} />
            <p>{items.length} item(s) in cart</p>
        </div>
    );
}

// Child receives the callback and invokes it — it doesn't own the cart
function ProductCard({ product, onAddToCart }) {
    return (
        <div className="product-card">
            <h3>{product.name}</h3>
            <button onClick={() => onAddToCart(product)}>Add to Cart</button>
        </div>
    );
}

💡 Naming convention

By convention, a prop that is an event handler is named onSomething (onAddToCart, onDelete), and the function that handles it is named handleSomething (handleAddToCart). It's not required, but it makes intent obvious at a glance.

Composition over Inheritance

In many languages you'd reach for class inheritance to share behavior. React deliberately avoids that. Instead you compose: build big components by nesting small ones and passing props. It's the difference between a rigid family tree of classes and a flexible box of interchangeable parts.

// A generic Dialog composed via props + children
function Dialog({ title, children }) {
    return (
        <div className="dialog">
            <h1>{title}</h1>
            {children}
        </div>
    );
}

// Specialize it by composition — no inheritance needed
function SignUpDialog() {
    return (
        <Dialog title="Sign Up">
            <input type="email" placeholder="Email" />
            <input type="password" placeholder="Password" />
            <button>Create account</button>
        </Dialog>
    );
}

A real composed example

A product list that reuses ProductCard — the same brick, rendered from data:

function ProductGrid({ products, onAddToCart }) {
    if (products.length === 0) {
        return <p>No products available.</p>;
    }
    return (
        <div className="product-grid">
            {products.map(product => (
                <ProductCard
                    key={product.id}
                    product={product}
                    onAddToCart={onAddToCart}
                />
            ))}
        </div>
    );
}

Practice & Quiz

🏋️ Exercise 1: A BlogPost component

Goal: Build BlogPost that takes title, author, date, and content props and renders them with semantic HTML.

// Usage target:
<BlogPost
    title="Getting Started with React"
    author="Jane Doe"
    date="2024-03-15"
    content="React is a powerful library..."
/>
💡 Hint

Destructure all four props in the parameter list. Wrap the output in an <article> with an <h2> for the title and a <p> for the byline.

✅ Solution
function BlogPost({ title, author, date, content }) {
    return (
        <article className="blog-post">
            <h2>{title}</h2>
            <p className="byline">
                By {author} · {new Date(date).toLocaleDateString()}
            </p>
            <p>{content}</p>
        </article>
    );
}

🏋️ Exercise 2: Report clicks upward

Goal: Make a LikeButton that doesn't store its own count. It receives a count prop and an onLike callback, and calls onLike when clicked. The parent owns the count.

💡 Hint

The child is "dumb": it just displays count and calls onLike() in onClick. The parent holds state with useState and passes both props down.

✅ Solution
function LikeButton({ count, onLike }) {
    return <button onClick={onLike}>👍 {count}</button>;
}

function Post() {
    const [likes, setLikes] = useState(0);
    return (
        <LikeButton count={likes} onLike={() => setLikes(likes + 1)} />
    );
}

The button is fully reusable because it owns no state — the parent decides what a "like" means.

🎯 Quick Quiz

Question 1: How does a child component send information back to its parent?

Question 2: What is the children prop?

Question 3: Which statement about props is true?

Best Practices & Pitfalls

✅ Do

  • Write small, single-purpose function components and capitalize their names
  • Destructure props and give sensible defaults
  • Treat props as read-only; derive new values instead of mutating
  • Lift state to the parent and pass callbacks down for events
  • Prefer composition (children + props) over inheritance

❌ Don't

  • Reassign or mutate a prop inside a component
  • Duplicate state a child could receive as a prop
  • Build deep class-inheritance hierarchies — compose instead
  • Forget the key when a component is rendered in a .map()

⚠️ "Lift state up"

When two components need the same data, don't copy it into both. Move the state up to their nearest common parent and pass it down as props. This "single source of truth" prevents the two copies from drifting out of sync — a pattern you'll use constantly once state hooks arrive.

Summary

🎉 Key Takeaways

  • Components are reusable functions that return JSX; capitalize their names
  • Props pass data from parent to child — strings in quotes, everything else in braces
  • Destructure props for readability and give defaults for optional ones
  • Props are read-only; data flows down, events flow up via callbacks
  • The children prop lets you build flexible wrapper components
  • Favor composition over inheritance — nest small components to build big ones

📚 Additional Resources

🚀 What's Next?

Your components are still frozen — they render, but nothing changes when a user interacts. Next you'll give them memory and interactivity with the most important hook in React: the useState hook.

🎉 You're building with bricks now!

Components and props are the foundation of every React app. Add state next, and your interfaces come alive.