100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
React 19 & Ecosystem
50 minintermediate

Foundation Practice: Interactive Component Library

What You'll Build

You will build a small, reusable component library that exercises everything from Module 1: a Button with variants, a controlled Input, a Card for composition, a Toggle with internal state, and a Modal that demonstrates conditional rendering and events. Each component is configured by props, manages its own state where appropriate, and handles events cleanly — a practical foundation you will reuse throughout the course.

The goal is to think in components: small, focused, prop-driven pieces that compose into richer UI. You will practise the props-versus-state decision, controlled inputs, event handlers that update state or call callbacks, and composition via children — the core skills every later module assumes.

Analogy🏏Cricket
🏏 Think of it like cricket: Just as a team first drills its fundamental skills — a clean cover drive, a tidy pickup-and-throw, a reliable catch — before combining them into match play, you first build clean, reusable components before composing them into features. The insight is that mastering the fundamentals in isolation makes the combined performance solid: a team with grooved basics plays fluent cricket, exactly as an app of well-built components composes into a fluent UI.

Prerequisites

  • Completion of lessons 01–04, or equivalent familiarity with components, JSX, props, state, and events.
  • Node.js 18+ and a React 19 project scaffolded with Vite (npm create vite@latest -- --template react).
  • Comfort running a dev server (npm run dev) and editing JSX files.
  • Basic CSS for light styling (or use plain class names you style later).
  • Understanding of useState and one-way data flow from this module.

Setup & Project Structure

Scaffold a React project with Vite and create a components folder to hold each reusable piece. You will build Button, Input, Card, Toggle, and Modal as separate files, then compose them in App to see them work together. Keep each component focused and prop-driven.

Analogy🏏Cricket
🏏 Think of it like cricket: before a tour you set up the training camp and give each specialist their own net — one lane for openers, one for the spinner, one for the death bowler — so each rehearses a focused, self-contained skill before you bring them together for a full practice match. Just as you scaffold a React project with Vite and a components folder holding Button, Input, Card, Toggle, and Modal as separate files, a camp organises separate stations for each role, each drilling one prop-driven job. Just as each component stays focused and driven by the props passed in, each net drill runs to a clear brief — 'defend the yorker', 'rotate strike' — nothing overloaded. And just as you finally compose them in App to see them work together, the camp ends with a full simulation where every rehearsed piece slots into one XI. The payoff: assembling a working whole is easy because every reusable part was built and tested in isolation first.

Decide up front what is props versus state for each: Button and Card are stateless (configured purely by props and children); Input is controlled by its parent (value + onChange props); Toggle and Modal own small pieces of state. This planning is the core skill the exercise reinforces.

bash
# Scaffold and run a React 19 + Vite project
npm create vite@latest component-lib -- --template react
cd component-lib && npm install && npm run dev

# Structure
# src/
# ├── components/
# │   ├── Button.jsx     # stateless, prop-driven (variant, onClick, children)
# │   ├── Input.jsx      # controlled (value, onChange props)
# │   ├── Card.jsx       # composition via children
# │   ├── Toggle.jsx     # owns boolean state
# │   └── Modal.jsx      # conditional render + events
# └── App.jsx            # composes them all

Step 1 — Stateless, Prop-Driven Button and Card

Build Button as a pure, stateless component configured entirely by props: a variant prop selects styling, children provides the label, and onClick passes through to the underlying button. Build Card similarly, using children to compose arbitrary content inside a styled container — demonstrating composition.

Neither component owns state; they are pure functions of their props, illustrating that many components need no state at all. The variant prop maps to a CSS class, and spreading remaining props (...rest) onto the element keeps the component flexible without enumerating every attribute.

Analogy🏏Cricket
🏏 Think of it like cricket: Just as a fielding position is defined once and simply occupied by whichever player is assigned, with no internal state of its own, a stateless component is defined once and configured purely by the props passed in. The insight is that many roles need no memory — the position is the same regardless of who fills it, exactly as a stateless Button renders purely from its props with nothing to remember between renders.
jsx
// Button.jsx — stateless, configured by props, composes via children
export function Button({ variant = "primary", children, ...rest }) {
  return (
    <button className={`btn btn-${variant}`} {...rest}>
      {children}
    </button>
  );
}

// Card.jsx — composition: render whatever children are passed inside
export function Card({ title, children }) {
  return (
    <div className="card">
      {title && <h3 className="card-title">{title}</h3>}
      <div className="card-body">{children}</div>
    </div>
  );
}

Step 2 — Controlled Input and Stateful Toggle

Build Input as a controlled component: it takes value and onChange props, so the parent owns the field's state and the input always reflects it. This is the canonical event-to-state loop — the input renders from value and reports changes via onChange — making the parent the single source of truth.

Build Toggle as a component that owns its own boolean state with useState, flipping it on click and optionally notifying a parent via an onChange callback. Contrast the two: Input is controlled by the parent (state lives up), while Toggle manages its own state (state lives in) — the props-versus-state decision made concrete.

Analogy🏏Cricket
🏏 Think of it like cricket: Just as a batter following the captain's strict instructions is 'controlled' from outside, while a batter improvising within their own judgement owns that decision internally, a controlled Input is driven by the parent's state while a Toggle owns its state. The insight is that responsibility can sit outside or inside depending on the role — some decisions are dictated, some are self-managed — and choosing correctly is exactly the props-versus-state call.
jsx
// Input.jsx — CONTROLLED: parent owns the value via props
export function Input({ value, onChange, ...rest }) {
  return (
    <input
      className="input"
      value={value}
      onChange={(e) => onChange(e.target.value)}   // report change up
      {...rest}
    />
  );
}

// Toggle.jsx — owns its own state, optionally notifies parent
import { useState } from "react";
export function Toggle({ defaultOn = false, onChange }) {
  const [on, setOn] = useState(defaultOn);
  function handle() {
    const next = !on;
    setOn(next);
    onChange?.(next);            // optional callback up
  }
  return <button aria-pressed={on} onClick={handle}>{on ? "On" : "Off"}</button>;
}

Step 3 — Modal with Conditional Rendering and Events

Build Modal to demonstrate conditional rendering and event handling: it takes an open boolean and an onClose callback, renders nothing when closed, and renders an overlay plus content when open. Clicking the overlay or a close button calls onClose, and clicking the inner content uses stopPropagation so it does not close.

The Modal does not own its open state — the parent does — keeping it controlled and reusable. This shows conditional rendering (return null when closed), event handling (onClose callbacks), and stopPropagation to prevent the overlay click from firing when the content is clicked, all patterns from the events lesson.

Analogy🏏Cricket
🏏 Think of it like cricket: Just as the players take the field only when the umpire signals the start, and leave when signalled, the Modal renders only when the parent signals it is open and disappears when closed. The insight is that presence is conditional on an external signal — the field is empty until play begins — exactly as the Modal renders nothing until its open prop says otherwise, with the controlling decision held outside it.
jsx
// Modal.jsx — controlled by parent; conditional render + event handling
export function Modal({ open, onClose, title, children }) {
  if (!open) return null;                       // conditional render

  return (
    <div className="overlay" onClick={onClose}>  {/* click backdrop to close */}
      <div className="modal" onClick={(e) => e.stopPropagation()}>  {/* don't close */}
        <header>
          <h3>{title}</h3>
          <Button variant="ghost" onClick={onClose}>×</Button>
        </header>
        <div>{children}</div>
      </div>
    </div>
  );
}

Step 4 — Testing & Verification

Compose all five components in App and verify each behaviour: Buttons render with variants and fire onClick; the controlled Input updates parent state on every keystroke; the Toggle flips and reports its state; and the Modal opens, closes via backdrop and button, and does not close when its content is clicked. Confirm that stateless components re-render purely from props and stateful ones manage their own state independently.

Analogy🏏Cricket
🏏 Think of it like cricket: the final selection trial where you field the full XI and check every player does their exact job under match conditions. Just as you compose all five components in App and verify each behaviour, a captain runs a scenario and confirms each role fires correctly: the buttons render variants and fire onClick like bowlers delivering their set variations on cue; the controlled Input updating parent state on every keystroke is the batter feeding the scorer every single run in real time; the Toggle flipping and reporting its state is the third-umpire light switching out and back and signalling the result. Just as the Modal opens, closes on the backdrop or button but not when its own content is clicked, a DRS review triggers on a genuine appeal, resolves cleanly, but isn't set off by incidental noise near the stumps. And just as stateless components re-render purely from props while stateful ones manage their own, pure specialists execute exactly the brief while others track their own tally. The payoff: verified, trustworthy behaviour before the real match.
jsx
// App.jsx — compose and exercise the library
import { useState } from "react";
import { Button } from "./components/Button";
import { Input } from "./components/Input";
import { Card } from "./components/Card";
import { Toggle } from "./components/Toggle";
import { Modal } from "./components/Modal";

export default function App() {
  const [name, setName] = useState("");          // parent owns Input's value
  const [open, setOpen] = useState(false);       // parent owns Modal's open state
  return (
    <Card title="Component Library">
      <Input value={name} onChange={setName} placeholder="Your name" />
      <Toggle defaultOn onChange={(on) => console.log("toggle:", on)} />
      <Button onClick={() => setOpen(true)}>Open Modal</Button>
      <Modal open={open} onClose={() => setOpen(false)} title={`Hi ${name || "there"}`}>
        <p>Composed from reusable components.</p>
      </Modal>
    </Card>
  );
}

Warning: Keep controlled components truly controlled — if Input takes a value prop, always pair it with onChange so the parent can update that value; otherwise the field appears frozen because state never changes. Mixing a value prop with no onChange (or switching between controlled and uncontrolled) produces React warnings and confusing input behaviour. Decide controlled or uncontrolled and stay consistent.

Extension Challenge: Add a controlled-vs-uncontrolled variant to Input by allowing it to manage its own state internally when no value prop is given (falling back to useState), making it work both ways. Then add keyboard handling to Modal — close it on the Escape key using an effect (previewing the next module) — and a Button loading state that disables the button and shows a spinner while an async onClick runs.

  • Build small, focused, reusable components configured by props; compose them via children.
  • Stateless components (Button, Card) are pure functions of props — many components need no state.
  • Controlled components (Input) take value + onChange so the parent owns the state (single source of truth).
  • Stateful components (Toggle) own state with useState and optionally report changes via a callback.
  • Conditional rendering (return null when closed) and event handling (onClose, stopPropagation) drive the Modal.
  • Decide props-versus-state deliberately per component, and keep controlled inputs consistently controlled.
Lesson 5 of 35
0% complete