Architecture
Component Composition
Build flexible components with composition
Intermediate
architecture components patterns react
Definition
Component Composition is the practice of building complex UIs by combining simpler components. Instead of creating monolithic components with many configuration options, composition allows you to assemble components like building blocks, providing flexibility and better separation of concerns.
Basic Composition
Containment
// Instead of:<Modal title="Settings" content={<Settings />} buttons={['Save', 'Cancel']} />
// Use children:
function Modal({ children }) {
return (
<div className="modal">
{children}
</div>
);
}
// Flexible usage:
<Modal>
<Modal.Header>Settings</Modal.Header>
<Modal.Body>
<SettingsForm />
</Modal.Body>
<Modal.Footer>
<Button>Save</Button>
<Button variant="secondary">Cancel</Button>
</Modal.Footer>
</Modal>
Specialization
// Base component
function Button({ variant, children, ...props }) {
return (
<button className={`btn btn-${variant}`} {...props}>
{children}
</button>
);
}
// Specialized components
function PrimaryButton(props) {
return <Button variant="primary" {...props} />;
}
function DangerButton(props) {
return <Button variant="danger" {...props} />;
}
// Usage
<PrimaryButton onClick={handleSave}>Save Changes</PrimaryButton>
<DangerButton onClick={handleDelete}>Delete</DangerButton>
Advanced Patterns
Compound Components
// Components that work together implicitly
function Select({ children }) {
const [selected, setSelected] = useState(null);
return (
<SelectContext.Provider value={{ selected, setSelected }}>
<div className="select">{children}</div>
</SelectContext.Provider>
);
}
Select.Trigger = function Trigger({ children }) {
const { selected } = useContext(SelectContext);
return <button>{selected || children}</button>;
};
Select.Option = function Option({ value, children }) {
const { setSelected } = useContext(SelectContext);
return <div onClick={() => setSelected(value)}>{children}</div>;
};
// Usage
<Select>
<Select.Trigger>Choose an option</Select.Trigger>
<Select.Option value="a">Option A</Select.Option>
<Select.Option value="b">Option B</Select.Option>
</Select>
Render Props
// Component that delegates rendering to a prop
function DataFetcher({ url, render }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(url)
.then(r => r.json())
.then(data => {
setData(data);
setLoading(false);
});
}, [url]);
return render({ data, loading });
}
// Usage
<DataFetcher
url="/api/users"
render={({ data, loading }) => (
loading ? <Spinner /> : <UserList users={data} />
)}
/>
Higher-Order Components
// Function that takes a component and returns enhanced component
function withAuth(Component) {
return function WrappedComponent(props) {
const user = useAuth();
if (!user) return <Login />;
return <Component {...props} user={user} />;
};
}
// Usage
function Dashboard({ user }) {
return <div>Welcome {user.name}</div>;
}
export default withAuth(Dashboard);
Best Practices
1. Prefer Composition Over Configuration
// ❌ Config-heavy
<Card
title="Product"
image="/product.jpg"
description="Great product"
actions={['buy', 'wishlist']}
layout="horizontal"
/>
// ✅ Composable
<Card>
<Card.Image src="/product.jpg" />
<Card.Content>
<Card.Title>Product</Card.Title>
<Card.Description>Great product</Card.Description>
</Card.Content>
<Card.Actions>
<Button>Buy</Button>
<Button>Wishlist</Button>
</Card.Actions>
</Card>
2. Props Delegation
function Input({ label, error, className, ...props }) {
return (
<div className={className}>
<label>{label}</label>
<input
className={error ? 'input-error' : 'input'}
{...props} // Pass all other props to input
/>
{error && <span className="error">{error}</span>}
</div>
);
}
// All input attributes work
<Input
label="Email"
type="email"
placeholder="Enter email"
required
onChange={handleChange}
error={errors.email}
/>
3. Slots Pattern
function PageLayout({ header, sidebar, children, footer }) {
return (
<div className="layout">
<header>{header}</header>
<aside>{sidebar}</aside>
<main>{children}</main>
<footer>{footer}</footer>
</div>
);
}
// Usage
<PageLayout
header={<Navigation />}
sidebar={<Filters />}
footer={<Footer />}
>
<ProductGrid />
</PageLayout>
Key Takeaway
Component composition provides flexibility by building UIs from smaller, reusable pieces. Use children for containment, create specialized components from generic ones, employ compound components for related UI elements, and prefer composition over extensive configuration options. This leads to more maintainable and adaptable codebases.