Component Composition
| Since: | React 16.8(2019) |
|---|
Component Composition in React is a design approach that builds complex UIs by combining small, independent components. Using composition rather than inheritance results in a component structure with high reusability and maintainability.
Main Composition Patterns
| Pattern | Description |
|---|---|
| Using children | The most basic composition pattern, where a parent component receives children and embeds arbitrary child elements inside itself. |
| Slots (multiple injection points) | A pattern that passes different components into each area of a layout via multiple props (e.g., header, footer). |
| Container & Presentation | A pattern that separates responsibilities: a container component handles data fetching and state management, while a presentation component handles only display. |
| Render Props | A pattern where a component receives a function as a prop and uses the return value of that function for rendering. Well-suited for sharing logic. |
Sample Code
An example of building a card UI using children and multiple slots (header and footer).
// Card component
// header and footer are slots that accept arbitrary JSX
// children receives the card body content
function Card({ header, footer, children }) {
const cardStyle = {
border: '1px solid #ddd',
borderRadius: '8px',
overflow: 'hidden',
width: '320px',
boxShadow: '0 2px 4px rgba(0,0,0,0.1)',
};
const headerStyle = {
background: '#4a90d9',
color: '#fff',
padding: '12px 16px',
fontWeight: 'bold',
};
const bodyStyle = {
padding: '16px',
};
const footerStyle = {
background: '#f5f5f5',
padding: '8px 16px',
borderTop: '1px solid #ddd',
fontSize: '13px',
color: '#666',
};
return (
<div style={cardStyle}>
{header && <div style={headerStyle}>{header}</div>}
<div style={bodyStyle}>{children}</div>
{footer && <div style={footerStyle}>{footer}</div>}
</div>
);
}
// UserProfile component
// Displays user information using Card
function UserProfile({ name, role, bio }) {
return (
<Card
header={name}
footer={<span>Role: {role}</span>}
>
<p>{bio}</p>
</Card>
);
}
// App component (root component)
function App() {
return (
<div style={{ display: 'flex', gap: '16px', padding: '24px' }}>
<UserProfile
name="Okabe Rintaro"
role="Future Gadget Lab, Director"
bio="A self-proclaimed mad scientist obsessed with time travel research."
/>
<UserProfile
name="Makise Kurisu"
role="Researcher"
bio="A genius girl majoring in neuroscience at an American graduate school."
/>
</div>
);
}
export default App;
Overview
The greatest advantage of component composition is the ability to keep components small and single-purpose while expressing diverse UIs through combination. In the sample above, Card handles only the layout skeleton, leaving the content entirely to the caller. This allows Card to be reused as a generic component elsewhere.
Composition via children is the simplest pattern. The "slot" pattern — receiving JSX through named props such as header and footer — provides a cleaner structure when multiple injection points exist. Both patterns are achieved using only ordinary props, without any special APIs.
Reusing functionality through inheritance (class extends) tends to create tight coupling between components and is not recommended. React's documentation strongly recommends using composition instead of inheritance. When logic sharing is needed, consider custom hooks. For injecting cross-cutting concerns, consider Higher-Order Components (HOC) or Render Props.
Related pages: children / props / Higher-Order Components (HOC) / Render Props / custom hooks
Common Mistakes
Excessive props passing to child components (props drilling)
When components are deeply nested, intermediate components that do not use a prop themselves are forced to relay it down to their children — a problem known as "props drilling." This can be resolved using Context or the composition pattern (passing elements as children).
drilling_ng.jsx
// Middle does not use userName itself but receives and passes it down
function Middle({ userName }) {
return <Bottom userName={userName} />;
}
function Bottom({ userName }) {
return <p>Logged in: {userName}</p>;
}
function App() {
const userName = 'Okabe Rintaro';
return <Middle userName={userName} />;
}
After fix:
drilling_ok.jsx
const UserContext = React.createContext('');
// Middle does not need to know about Context — it just renders children
function Middle({ children }) {
return <div>{children}</div>;
}
function Bottom() {
// The component that needs it reads directly from Context
const userName = React.useContext(UserContext);
return <p>Logged in: {userName}</p>;
}
function App() {
return (
<UserContext.Provider value="Okabe Rintaro">
<Middle>
<Bottom />
</Middle>
</UserContext.Provider>
);
}
Defining a component inline causing remounting on every render
Defining a new component inside a render function creates a different component type on every render, causing repeated unmount and remount. Always define components at the module's top level.
inline_ng.jsx
function App() {
// A new type is defined on every re-render of App
function Profile({ name }) {
return <p>{name}</p>;
}
return <Profile name="Makise Kurisu" />;
}
After fix:
inline_ok.jsx
// Defining at the top level keeps the component type stable and prevents remounting
function Profile({ name }) {
return <p>{name}</p>;
}
function App() {
return <Profile name="Makise Kurisu" />;
}
Reaching for inheritance instead of composition
Trying to reuse components via class extends in React tends to create tight coupling between components. Composition using the children prop or the render props pattern is more flexible and produces a more maintainable design.
inheritance_ng.jsx
class BaseCard extends React.Component {
render() {
return <div className="card">{this.renderContent()}</div>;
}
}
// Trying to reuse the common layout through inheritance
class ProfileCard extends BaseCard {
renderContent() {
return <p>{this.props.name}</p>;
}
}
After fix:
inheritance_ok.jsx
// Make a generic card component that accepts children
function Card({ children }) {
return <div className="card">{children}</div>;
}
// Use composition with Card rather than inheritance
function ProfileCard({ name }) {
return (
<Card>
<p>{name}</p>
</Card>
);
}
function App() {
return <ProfileCard name="Shiina Mayuri" />;
}
If you find any errors or copyright issues, please contact us.