Runnable lesson · press Try it on the examples to edit and run them here.
Composition means building bigger components out of smaller ones. It is the main way React shares code. React does not use class inheritance for this, and you will not need it.
Containment: components that wrap content
Some components do not know their content in advance. A Card, a Panel or a Modal wraps whatever you give it. You pass the content between the tags, and the component receives it as children:
function Card({ title, children }) {
return (
<section style={{ border: "1px solid #ccc", borderRadius: 8, padding: 16, maxWidth: 320 }}>
<h2 style={{ marginTop: 0 }}>{title}</h2>
{children}
</section>
);
}
function App() {
return (
<Card title="Card title">
<p>Any content can go in here.</p>
<button>A button</button>
</Card>
);
}
Card handles the frame, App provides the content. The next lesson, Passing Children, covers children in detail.
Slots: several places to fill
If a component has more than one place to fill, pass elements as props. React elements are values, so a prop can hold JSX:
function Layout({ header, sidebar, children }) {
return (
<div style={{ display: "grid", gridTemplateColumns: "140px 1fr", gap: 12 }}>
<div style={{ gridColumn: "1 / -1", background: "#f4f4f4", padding: 8 }}>{header}</div>
<aside style={{ background: "#fafafa", padding: 8 }}>{sidebar}</aside>
<main>{children}</main>
</div>
);
}
function App() {
return (
<Layout
header={<h1 style={{ margin: 0 }}>Dashboard</h1>}
sidebar={<nav>Menu goes here</nav>}
>
<p>Main content</p>
</Layout>
);
}
Layout decides the arrangement and the spacing. It does not care what header or sidebar contain.
Specialisation: a specific version of a general component
A general component can be configured through props, and a more specific component can wrap it:
function Dialog({ title, message, children }) {
return (
<div role="dialog" aria-label={title} style={{ border: "1px solid #999", padding: 12, maxWidth: 300 }}>
<h3 style={{ marginTop: 0 }}>{title}</h3>
<p>{message}</p>
{children}
</div>
);
}
function ConfirmDeleteDialog() {
return (
<Dialog title="Delete account" message="This cannot be undone.">
<button>Cancel</button> <button>Delete</button>
</Dialog>
);
}
function App() {
return <ConfirmDeleteDialog />;
}
ConfirmDeleteDialog is a specialised Dialog. There is no inheritance and no extends. It simply uses Dialog and fills in the details.
Why composition and not inheritance?
In class-based languages you may be used to making ConfirmDialog extend Dialog. In React, props and children already cover the cases inheritance is used for, and they are easier to follow. The React documentation says that, across thousands of components at Facebook, its authors found no use case where they would recommend component inheritance hierarchies, and it recommends composition instead.
Good composition habits
- Keep layout components free of data.
Layoutarranges; it does not fetch. - Prefer passing elements over passing flags. Instead of
<Card showFooter footerText="..." footerButton />, acceptfooter={<Footer ... />}. - Do not repeat yourself. If two components share structure, extract a third that both use.
- Keep the tree shallow enough to read. A tree five levels deep is fine; ten levels of single-child wrappers is a hint to simplify.
Key points
- Composition is how React builds big components from small ones.
- Use
childrenfor one content area and props that hold JSX for several slots. - Make specific components by wrapping general ones, not by inheritance.