Component Composition
Component Composition
Component composition is one of the most powerful patterns in React. It refers to building complex components by combining simpler, smaller, and reusable components. React embraces this concept fully, allowing you to treat components just like standard HTML elements.
The “Is-A” vs “Has-A” Relationship
In object-oriented programming (like Java), you often use Inheritance to share code (e.g., a Dialog is a Box).
In React, we use Composition to share code (e.g., a Dialog has a Box inside it). React strongly recommends using composition instead of inheritance.
Containment (Using the children prop)
Some components don’t know their children ahead of time. This is common for generic “boxes” like a Sidebar, Dialog, or Card.
These components can use the special children prop to pass children elements directly into their output:
function Dialog({ title, children }) {
return (
<div className="dialog-overlay">
<div className="dialog-box">
<h1 className="dialog-title">{title}</h1>
<div className="dialog-content">
{children} {/* Renders whatever is passed inside the <Dialog> tags */}
</div>
</div>
</div>
);
}
// Composition in action
function WelcomeDialog() {
return (
<Dialog title="Welcome">
<p>Thank you for visiting our spacecraft!</p>
<button>Explore</button>
</Dialog>
);
}
Specialization
Sometimes we think about components as being “special cases” of other components. For example, a WelcomeDialog might be a special case of Dialog.
In React, this is also achieved by composition, where a more “specific” component renders a more “generic” one and configures it with props:
function WelcomeDialog() {
return (
<Dialog
title="Welcome"
message="Thank you for visiting our spacecraft!"
/>
);
}
Multiple “Holes” (Slots)
Sometimes children isn’t enough, and you need multiple “slots” or “holes” to fill in a generic component. You can pass React elements as regular props!
function SplitPane({ left, right }) {
return (
<div className="split-pane">
<div className="split-pane-left">{left}</div>
<div className="split-pane-right">{right}</div>
</div>
);
}
function App() {
return (
<SplitPane
left={<Contacts />}
right={<Chat />}
/>
);
}
This pattern avoids the need for complex configuration objects or inheritance hierarchies. React elements like <Contacts /> and <Chat /> are just JavaScript objects, so you can pass them around like any other data.
Interview Questions
Q: How does Component Composition solve the problem of “Prop Drilling”?
A: Instead of passing state down through 5 layers of intermediate components (Prop Drilling), you can lift the child component up to the top level, inject the state into it, and pass the fully-formed component down as a child or a explicit prop. The intermediate components just render {children} and don’t need to know about the props at all.
Q: What is the difference between specialized components and generic components in composition?
A: Generic components (like Dialog, Sidebar) don’t know their content ahead of time and use the children prop. Specialized components (like WelcomeDialog) render generic components and configure them with specific props and children. This mirrors class inheritance but is achieved purely through composition.