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.