Composition & Reuse
React has a powerful but simple model for reusing code between components: composition. Instead of extending base classes, you assemble small, focused components into larger ones, passing UI and behavior through props. This keeps each piece independent and testable while letting you build arbitrarily complex interfaces. The official guidance is unambiguous: prefer composition over inheritance.
Composition over inheritance
In many object-oriented frameworks you reuse code by subclassing—a FancyButton extends Button. React deliberately avoids this. Components are functions, and the natural way to reuse a function’s output is to call it and combine the results, not to inherit from it.
Suppose you have a generic Button and want a styled variant. Don’t reach for inheritance; wrap it.
function Button({ variant = "default", children, ...rest }) {
return (
<button className={`btn btn-${variant}`} {...rest}>
{children}
</button>
);
}
// A specialized button is just a component that delegates to Button
function DangerButton(props) {
return <Button variant="danger" {...props} />;
}
DangerButton reuses every line of Button without an inheritance hierarchy. If Button gains a feature, DangerButton gets it for free.
Tip: The React team has stated they have never found a use case where component inheritance was preferable to composition. If you feel tempted to subclass a component, extract a shared component or a plain JavaScript helper instead.
Containment with children
Some components are “containers”—they don’t know their contents ahead of time. A Card, Modal, or Sidebar should accept whatever you nest inside it. React passes that nested JSX through the special children prop.
function Card({ title, children }) {
return (
<section className="card">
<h2 className="card-title">{title}</h2>
<div className="card-body">{children}</div>
</section>
);
}
// usage
<Card title="Release notes">
<p>Version 19 ships the new compiler.</p>
<DangerButton>Dismiss</DangerButton>
</Card>
Output:
┌─ Release notes ────────────────────┐
│ Version 19 ships the new compiler. │
│ [ Dismiss ] │
└────────────────────────────────────┘
Anything between the opening and closing tags becomes children. The Card stays generic and reusable across every part of your app.
Slot-style props
When a component needs several distinct regions, a single children prop isn’t enough. Pass JSX through named props—each prop acts like a “slot.” This is React’s idiomatic answer to the slot patterns found in other frameworks.
function SplitPane({ left, right }) {
return (
<div className="split-pane">
<div className="split-pane-left">{left}</div>
<div className="split-pane-right">{right}</div>
</div>
);
}
// usage
<SplitPane
left={<Navigation />}
right={<Editor document={doc} />}
/>
Because JSX elements are ordinary JavaScript values, you can store them in variables, pass them as props, and return them from functions. That flexibility is what makes slot-style composition possible.
Specialization via props
A “specific” component is often just a “generic” component configured through props. Rather than copying markup, render the general component and feed it the specifics.
function Dialog({ title, message, children }) {
return (
<div className="dialog">
<h1 className="dialog-title">{title}</h1>
<p className="dialog-message">{message}</p>
{children}
</div>
);
}
function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thanks for trying DevCraftly!"
>
<Button variant="primary">Get started</Button>
</Dialog>
);
}
WelcomeDialog is a specialization of Dialog. It adds no new structure—only configuration and an extra child.
Extracting components for reuse
When a component grows unwieldy or a piece of its JSX repeats, extract that piece into its own component. The signal to extract is usually duplication, a clear self-contained responsibility, or a chunk that you’d like to test or reuse on its own.
function Comment({ author, text, date }) {
return (
<div className="comment">
<UserInfo user={author} />
<div className="comment-text">{text}</div>
<time className="comment-date">{date}</time>
</div>
);
}
// Extracted because avatar + name appears in many places
function UserInfo({ user }) {
return (
<div className="user-info">
<Avatar src={user.avatarUrl} alt={user.name} />
<span className="user-name">{user.name}</span>
</div>
);
}
Extracting UserInfo lets you drop the same author block into comments, profiles, and notifications without rewriting it.
Composition patterns at a glance
| Pattern | When to use | Mechanism |
|---|---|---|
| Containment | One generic content area | children prop |
| Slots | Multiple named regions | JSX passed via named props |
| Specialization | A configured variant of a generic component | Render the generic, pass props |
| Extraction | Repeated or self-contained markup | Pull JSX into a new component |
Best Practices
- Reach for composition first; treat component inheritance as a code smell.
- Keep container components generic—pass content in through
childrenor slot props rather than hardcoding it. - Use named slot props when a component has more than one distinct region.
- Build specific components by configuring generic ones through props, not by duplicating markup.
- Extract a new component the moment markup repeats or a section earns its own clear responsibility.
- Remember that JSX is just a value—you can store it in variables and pass it around freely.
- Favor many small, focused components over a few large ones; small pieces compose and test more easily.