Skip to content

Lesson 3 — Props, composition, and component contracts

Objective

Create a reusable progress indicator with a narrow, accessible contract.

Prerequisites

Lesson 2 — Rendering, lists, and conditional UI. You should understand mapping arrays and using controlled inputs.

Mental Model

Props are read-only inputs for one render. Composition keeps policy at the owner while a child handles presentation. Callback props report intent; children do not mutate parent state.

Concept

A stable component contract is small, focused, and hard to misuse. The parent owns the state and passes derived display values. The child receives primitives, calls callbacks to report events, and keeps its own markup independent of the parent's domain.

Example

function ProgressRing({ value, label }: { value: number; label: string }) {
  const clamped = Math.min(100, Math.max(0, value));
  return (
    <div role="img" aria-label={`${label}: ${clamped}%`}>
      <svg viewBox="0 0 100 100" aria-hidden="true">
        <circle cx="50" cy="50" r="40" fill="none" stroke="currentColor" strokeWidth="8" />
      </svg>
    </div>
  );
}

Real-World Usage

Meters, badges, buttons, and cards that are reused across a dashboard but must stay independent of specific data shapes.

Common Mistakes

  • Accepting the whole application store as a prop.
  • Adding boolean configuration props for every variation instead of using composition.
  • Letting children mutate parent state directly.

Mini Lab

Task

Create ProgressRing with numeric value and text label. Clamp or reject invalid values deliberately. Use it on the dashboard, then extract one topic card without making a universal component.

Constraints

  • The accessible name communicates label and percentage.
  • Invalid props fail at compile time.
  • Presentation stays independent of tracker state.

Expected Result

ProgressRing renders a clamped percentage with an accessible name and can be placed in the dashboard without importing tracker logic.

Hints

Pass primitive display values instead of the entire store. Prefer composition over growing boolean configuration props.

Knowledge Check

  • When is prop drilling clearer than Context?
  • What makes a component API stable?
  • How do callback identity and closures affect children?

Challenge

A PM wants a single <Card> that renders topics, sessions, notifications, and alerts. How would you redesign the contract to avoid brittle isTopic, isSession props?

Summary

Props are read-only primitives and callbacks. Composition and narrow contracts produce stable, reusable UI pieces.

What Comes Next

Lesson 4 — Hooks, reducer state, and derived data