Skip to content

Component Composition

Composition is how React builds complexity from simplicity. Instead of one large component that does everything, you build many small components and compose them together. Each layer adds one piece of behavior or structure.

React passes any JSX placed between a component’s opening and closing tags as a special children prop:

function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}

Now Card is a reusable wrapper. You decide what goes inside at the call site:

<Card>
<h2>Income</h2>
<p>$4,300 this month</p>
</Card>
<Card>
<h2>Housing</h2>
<p>$1,200 budgeted</p>
</Card>

Both render a card-styled wrapper with different content. The Card component does not need to know what it wraps — that is the caller’s decision.

The top section of ZeroBudget has two parts side by side: the income panel and the left-to-assign number. In App.jsx:

function App() {
return (
<div className="app">
<header className="app-header">
<h1>ZeroBudget</h1>
</header>
<main className="app-main">
<MonthNav />
<div className="app-top">
<IncomeSection incomeSources={sources} />
<LeftToAssign amount={leftToAssign} />
</div>
<section className="categories-section">
{categories.map(cat => (
<CategoryCard key={cat.id} category={cat} />
))}
</section>
</main>
</div>
);
}

App orchestrates layout. Each child component handles its own content. No single component is responsible for everything.

Sometimes you want to compose by spreading props through:

function LabeledInput({ label, ...inputProps }) {
return (
<div className="field">
<label>{label}</label>
<input {...inputProps} />
</div>
);
}
<LabeledInput label="Amount" type="number" placeholder="0.00" step="0.01" />

...inputProps collects all remaining props and spreads them onto the <input>. The LabeledInput component does not need to list every possible input attribute — it just passes them through.

Coming from object-oriented languages, you might reach for inheritance to share behavior between components. React explicitly recommends composition instead. Components share behavior by accepting and using other components, not by extending them.

If two components share logic (not UI), extract the logic into a custom hook (covered in Module 06). If they share UI structure, extract it into a shared component that accepts children.

  1. Create a Card component in src/components/Card/Card.jsx that wraps its children in a <div className="card">.
  2. Update IncomeSection to use <Card> as its outer wrapper instead of a plain <section>.
  3. Create a placeholder CategoryCard component that also uses <Card>. Pass a category prop with a name property and render the name inside the card.
  4. Render three CategoryCard components in App.jsx using a hardcoded categories array.
  • Composition builds complex UIs from small, focused components.
  • The children prop holds any JSX passed between a component’s opening and closing tags.
  • Spread remaining props with ...rest to create pass-through wrapper components.
  • Prefer composition over inheritance — share UI structure with children, share logic with custom hooks.