The true power of React isn't in individual components - it's in how you compose them together. Just like building with LEGO blocks, you create complex UIs by combining simple, focused components. This is called component composition, and it's the key to building maintainable, scalable React applications. In this lesson, you'll learn composition patterns, best practices, and how to architect your components for maximum reusability.
What is Component Composition?
Component composition means building complex components by combining simpler ones, rather than building one massive component that does everything.
The Problem: Monolithic Components
// ❌ Bad - One giant component doing everything
function UserDashboard() {
return (
<div className="dashboard">
{/* Header */}
<header>
<img src="/logo.png" alt="Logo" />
<nav>
<a href="/home">Home</a>
<a href="/profile">Profile</a>
<a href="/settings">Settings</a>
</nav>
<button>Logout</button>
</header>
{/* Sidebar */}
<aside>
<h2>Filters</h2>
<label>
<input type="checkbox" /> Active
</label>
<label>
<input type="checkbox" /> Completed
</label>
</aside>
{/* Main content */}
<main>
<h1>Tasks</h1>
<div className="task-list">
{/* 50+ lines of task rendering... */}
</div>
</main>
{/* Footer */}
<footer>
<p>© 2024 My App</p>
</footer>
</div>
)
}
// This component:
// - Is hard to read (hundreds of lines)
// - Is hard to test
// - Can't reuse parts
// - Is hard to maintainThe Solution: Composition
// ✅ Good - Small, focused components
function Logo() {
return <img src="/logo.png" alt="Logo" />
}
function Navigation() {
return (
<nav>
<a href="/home">Home</a>
<a href="/profile">Profile</a>
<a href="/settings">Settings</a>
</nav>
)
}
function Header() {
return (
<header>
<Logo />
<Navigation />
<button>Logout</button>
</header>
)
}
function Sidebar() {
return (
<aside>
<h2>Filters</h2>
<FilterCheckbox label="Active" />
<FilterCheckbox label="Completed" />
</aside>
)
}
function TaskList() {
return (
<div className="task-list">
{/* Task rendering */}
</div>
)
}
function Footer() {
return (
<footer>
<p>© 2024 My App</p>
</footer>
)
}
// Main component - clean and readable!
function UserDashboard() {
return (
<div className="dashboard">
<Header />
<Sidebar />
<main>
<h1>Tasks</h1>
<TaskList />
</main>
<Footer />
</div>
)
}
// Each component:
// - Has a single responsibility
// - Is easy to read and understand
// - Can be reused elsewhere
// - Is easy to test and maintainComponent Composition Hierarchy
Composition Principles
1. Single Responsibility
Each component should do one thing well:
// ❌ Component doing too much
function UserProfile() {
return (
<div>
{/* Profile info */}
<div className="profile">...</div>
{/* Posts */}
<div className="posts">...</div>
{/* Friends list */}
<div className="friends">...</div>
{/* Settings */}
<div className="settings">...</div>
</div>
)
}
// ✅ Each component has one job
function ProfileInfo() {
return <div className="profile">...</div>
}
function PostsList() {
return <div className="posts">...</div>
}
function FriendsList() {
return <div className="friends">...</div>
}
function Settings() {
return <div className="settings">...</div>
}
function UserProfile() {
return (
<div>
<ProfileInfo />
<PostsList />
<FriendsList />
<Settings />
</div>
)
}2. Compose from the Bottom Up
Build small components first, then combine them into larger ones:
// Step 1: Smallest components (atoms)
function Button({ children, onClick }) {
return (
<button className="btn" onClick={onClick}>
{children}
</button>
)
}
function Input({ label, value, onChange }) {
return (
<div>
<label>{label}</label>
<input value={value} onChange={onChange} />
</div>
)
}
// Step 2: Combine into medium components (molecules)
function SearchBox({ value, onChange, onSearch }) {
return (
<div className="search-box">
<Input
label="Search"
value={value}
onChange={onChange}
/>
<Button onClick={onSearch}>Search</Button>
</div>
)
}
// Step 3: Combine into larger components (organisms)
function Header({ searchValue, onSearch, onSearchChange }) {
return (
<header>
<Logo />
<SearchBox
value={searchValue}
onChange={onSearchChange}
onSearch={onSearch}
/>
<UserMenu />
</header>
)
}
// Step 4: Compose into pages (templates)
function HomePage() {
return (
<div>
<Header />
<MainContent />
<Footer />
</div>
)
}3. Use Props for Customization
// ✅ Reusable through props
function Card({ title, image, description, action }) {
return (
<div className="card">
{image && <img src={image} alt={title} />}
<h3>{title}</h3>
<p>{description}</p>
{action && <div className="actions">{action}</div>}
</div>
)
}
// Same component, different uses
<Card
title="Product 1"
image="/product1.jpg"
description="Great product"
action={<Button>Buy Now</Button>}
/>
<Card
title="Article"
description="Interesting article"
action={<Button>Read More</Button>}
/>
<Card
title="Simple Card"
description="No image, no action"
/>4. Use Children for Flexibility
// ✅ Maximum flexibility with children
function Panel({ title, children }) {
return (
<div className="panel">
<div className="panel-header">{title}</div>
<div className="panel-body">{children}</div>
</div>
)
}
// Flexible usage
<Panel title="User Settings">
<SettingsForm />
</Panel>
<Panel title="Statistics">
<Chart data={chartData} />
<Legend />
</Panel>
<Panel title="Notifications">
<p>You have 5 new notifications</p>
<NotificationList />
<Button>Mark All Read</Button>
</Panel>Common Composition Patterns
1. Container and Presentational Components
Separate logic (containers) from presentation (components):
// Presentational - just displays data
function UserList({ users, onUserClick }) {
return (
<ul>
{users.map(user => (
<li key={user.id} onClick={() => onUserClick(user)}>
{user.name}
</li>
))}
</ul>
)
}
// Container - handles logic and state
function UserListContainer() {
const [users, setUsers] = React.useState([])
const [loading, setLoading] = React.useState(true)
React.useEffect(() => {
fetch('/api/users')
.then(res => res.json())
.then(data => {
setUsers(data)
setLoading(false)
})
}, [])
const handleUserClick = (user) => {
console.log('Clicked:', user)
}
if (loading) return <div>Loading...</div>
return <UserList users={users} onUserClick={handleUserClick} />
}2. Layout Components
// Reusable layout components
function TwoColumnLayout({ left, right }) {
return (
<div className="two-column">
<div className="left-column">{left}</div>
<div className="right-column">{right}</div>
</div>
)
}
function PageLayout({ header, sidebar, content, footer }) {
return (
<div className="page">
<header>{header}</header>
<div className="page-body">
<aside>{sidebar}</aside>
<main>{content}</main>
</div>
<footer>{footer}</footer>
</div>
)
}
// Usage
<PageLayout
header={<Header />}
sidebar={<Sidebar />}
content={<MainContent />}
footer={<Footer />}
/>3. Specialized Components
// Generic button
function Button({ variant = 'primary', children, ...props }) {
return (
<button className={`btn btn-${variant}`} {...props}>
{children}
</button>
)
}
// Specialized buttons built from generic Button
function PrimaryButton({ children, ...props }) {
return <Button variant="primary" {...props}>{children}</Button>
}
function DangerButton({ children, ...props }) {
return <Button variant="danger" {...props}>{children}</Button>
}
function SubmitButton({ children, ...props }) {
return (
<PrimaryButton type="submit" {...props}>
{children || 'Submit'}
</PrimaryButton>
)
}
// Usage
<PrimaryButton onClick={handleSave}>Save</PrimaryButton>
<DangerButton onClick={handleDelete}>Delete</DangerButton>
<SubmitButton /> {/* Uses default "Submit" text */}4. Higher-Order Composition
// Wrapper component that adds functionality
function withLoading(Component) {
return function LoadingWrapper({ isLoading, ...props }) {
if (isLoading) {
return <div>Loading...</div>
}
return <Component {...props} />
}
}
// Original component
function UserProfile({ user }) {
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
)
}
// Enhanced component with loading
const UserProfileWithLoading = withLoading(UserProfile)
// Usage
<UserProfileWithLoading
isLoading={loading}
user={userData}
/>Practical Example: Building a Card System
💡 Tip: Edit the code above and click "Run" to see your changes
Composition vs Inheritance
In traditional object-oriented programming, you use inheritance to reuse code. In React, we prefer composition:
❌ Inheritance (Not Recommended in React)
// ❌ Don't do this in React
class BaseButton extends React.Component {
render() {
return <button className="btn">{this.props.children}</button>
}
}
class PrimaryButton extends BaseButton {
render() {
return <button className="btn btn-primary">{this.props.children}</button>
}
}
// Problems:
// - Tight coupling
// - Hard to modify base class
// - Can't easily combine multiple behaviors✅ Composition (Recommended)
// ✅ Do this instead
function Button({ variant = 'default', children, ...props }) {
return (
<button
className={`btn btn-${variant}`}
{...props}
>
{children}
</button>
)
}
// Just use props for variations
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
// Easy to combine behaviors
<Button variant="primary" disabled>Disabled Primary</Button>
<Button variant="danger" onClick={handleDelete}>Delete</Button>React Philosophy:
"Prefer composition over inheritance." React makes composition so easy and powerful that you rarely (if ever) need inheritance. Props and children give you all the flexibility you need!
Designing Component Hierarchies
Think in Components
When designing a UI, break it down into a component hierarchy:
- Draw boxes around every component and sub-component
- Name each component based on what it does
- Arrange them in a hierarchy - which components contain others?
- Identify what data each component needs
- Build from the bottom up - small to large
Example: Product Page Breakdown
ProductPage
├── Header
│ ├── Logo
│ ├── SearchBar
│ │ ├── Input
│ │ └── SearchButton
│ └── UserMenu
│ ├── Avatar
│ └── Dropdown
├── ProductDetails
│ ├── ImageGallery
│ │ ├── MainImage
│ │ └── ThumbnailList
│ │ └── Thumbnail (×4)
│ ├── ProductInfo
│ │ ├── ProductTitle
│ │ ├── Rating
│ │ │ └── Star (×5)
│ │ ├── Price
│ │ └── Description
│ └── PurchasePanel
│ ├── QuantitySelector
│ ├── AddToCartButton
│ └── WishlistButton
├── Reviews
│ ├── ReviewSummary
│ └── ReviewList
│ └── ReviewCard (×many)
│ ├── UserInfo
│ ├── Rating
│ └── ReviewText
└── Footer
├── FooterLinks
└── CopyrightComposition Best Practices
1. Keep Components Small
// ❌ Too large
function ProductCard() {
return (
<div>
{/* 100+ lines of JSX */}
</div>
)
}
// ✅ Split into smaller pieces
function ProductImage({ src, alt }) {
return <img src={src} alt={alt} />
}
function ProductTitle({ children }) {
return <h3>{children}</h3>
}
function ProductPrice({ price }) {
return <p className="price">${price}</p>
}
function ProductCard({ product }) {
return (
<div className="card">
<ProductImage src={product.image} alt={product.name} />
<ProductTitle>{product.name}</ProductTitle>
<ProductPrice price={product.price} />
<AddToCartButton productId={product.id} />
</div>
)
}
// Rule of thumb: If a component is >100 lines, split it2. Name Components Descriptively
// ❌ Vague names
function Box() { }
function Item() { }
function Thing() { }
// ✅ Descriptive names
function UserProfileCard() { }
function ProductListItem() { }
function NavigationMenu() { }3. Avoid Prop Drilling
// ❌ Prop drilling - passing props through many levels
function App() {
const user = { name: 'Alice' }
return <Layout user={user} />
}
function Layout({ user }) {
return <Header user={user} />
}
function Header({ user }) {
return <UserMenu user={user} />
}
function UserMenu({ user }) {
return <div>{user.name}</div>
}
// ✅ Better solutions:
// 1. Composition - pass components, not data
function App() {
const user = { name: 'Alice' }
return (
<Layout>
<Header>
<UserMenu user={user} />
</Header>
</Layout>
)
}
// 2. Context (we'll learn this later)
// 3. State management libraries4. Compose with Intention
// ❌ Random composition
function Page() {
return (
<div>
<Button />
<Card>
<Header />
<Input />
</Card>
<Footer />
</div>
)
}
// ✅ Logical, intentional composition
function Page() {
return (
<PageLayout>
<Header />
<MainContent>
<Card>
<Form>
<Input />
<Button>Submit</Button>
</Form>
</Card>
</MainContent>
<Footer />
</PageLayout>
)
}
// Clear hierarchy and relationships5. Extract When You Repeat
// ❌ Repetitive code
function Dashboard() {
return (
<div>
<div className="stat-card">
<h3>Users</h3>
<p className="number">1,234</p>
</div>
<div className="stat-card">
<h3>Revenue</h3>
<p className="number">$45,678</p>
</div>
<div className="stat-card">
<h3>Orders</h3>
<p className="number">567</p>
</div>
</div>
)
}
// ✅ Extract into reusable component
function StatCard({ title, value }) {
return (
<div className="stat-card">
<h3>{title}</h3>
<p className="number">{value}</p>
</div>
)
}
function Dashboard() {
return (
<div>
<StatCard title="Users" value="1,234" />
<StatCard title="Revenue" value="$45,678" />
<StatCard title="Orders" value="567" />
</div>
)
}Key Takeaways
- Composition means building complex UIs from simple components
- Each component should have a single responsibility
- Build components from the bottom up (small to large)
- Use props and children for customization and flexibility
- Prefer composition over inheritance in React
- Keep components small (under 100 lines when possible)
- Separate container (logic) from presentational (display) components
- Use layout components for consistent structure
- Create specialized components from generic ones
- Extract repetitive code into reusable components
- Design component hierarchies before coding
- Avoid prop drilling with composition or context
- Name components descriptively based on what they do
- Composition makes code more maintainable and testable
What's Next?
Congratulations! You've completed the Components Basics section. You now understand:
- How to create function components
- How to pass and use props
- How to work with the children prop
- How to destructure props for cleaner code
- How to compose components into complex UIs
But there's still a big piece missing: your components are still static. They can't remember things or respond to user interactions. That's where state comes in!
In the next section, you'll learn about state - how to make your components interactive, dynamic, and truly alive. Get ready to build components that respond to clicks, remember user input, and update themselves! 🚀