
Compound Components
Matan Shaviro / February 23, 2025
Introduction
The compound component pattern is a powerful technique in React that allows us to create flexible and reusable UI components. It provides a way for components to communicate with each other implicitly using React's context. In this post, we'll implement a Product Card component following the compound component pattern.
This pattern is particularly useful for UI components that require multiple subcomponents, such as modals, dropdowns, and cards. Inspired by this video, we'll build a Product Card instead of a Post Card.
What is the Compound Component Pattern?
A compound component is a set of components that work together under a common parent but expose a flexible API to users. Instead of passing a lot of props to configure the component, we use composition to define its structure.
Example of a Product Card
A Product Card might have the following subcomponents:
<ProductCard>
- The main wrapper that provides context.<ProductCard.Image>
- Displays the product image.<ProductCard.Title>
- Shows the product title.<ProductCard.Price>
- Displays the product price.<ProductCard.Actions>
- Contains buttons like "Add to Cart" or "Wishlist".
Implementation
Let's implement the ProductCard
using the compound component pattern.
Step 1: Create Context and Provider
First, we'll create a context to share data between subcomponents.
import { createContext, useContext, ReactNode } from 'react'
interface ProductCardContextType {
title: string
image: string
price: string
}
const ProductCardContext = createContext<ProductCardContextType | null>(null)
interface ProductCardProps {
title: string
image: string
price: string
children: ReactNode
}
export function ProductCard({
title,
image,
price,
children
}: ProductCardProps) {
return (
<ProductCardContext.Provider value={{ title, image, price }}>
<div className='max-w-sm rounded-lg border p-4 shadow-lg'>{children}</div>
</ProductCardContext.Provider>
)
}
function useProductCard() {
const context = useContext(ProductCardContext)
if (!context) {
throw new Error('ProductCard components must be used within a ProductCard')
}
return context
}
Step 2: Create Subcomponents
Now, we'll create the subcomponents that consume the context.
ProductCard.Image
export function Image() {
const { image, title } = useProductCard()
return (
<img
src={image}
alt={title}
className='h-48 w-full rounded-md object-cover'
/>
)
}
ProductCard.Title
export function Title() {
const { title } = useProductCard()
return <h2 className='mt-2 text-lg font-bold'>{title}</h2>
}
ProductCard.Price
export function Price() {
const { price } = useProductCard()
return <p className='font-semibold text-green-600'>${price}</p>
}
ProductCard.Actions
export function Actions({ children }: { children: ReactNode }) {
return <div className='mt-4 flex gap-2'>{children}</div>
}
Step 3: Export All Components
We'll attach the subcomponents to ProductCard
for a clean API.
ProductCard.Image = Image
ProductCard.Title = Title
ProductCard.Price = Price
ProductCard.Actions = Actions
Step 4: Use the ProductCard Component
Now, let's use our compound component in an application.
export default function App() {
return (
<ProductCard title='Awesome Sneakers' image='/sneakers.jpg' price='99.99'>
<ProductCard.Image />
<ProductCard.Title />
<ProductCard.Price />
<ProductCard.Actions>
<button className='rounded bg-blue-500 px-4 py-2 text-white'>
Add to Cart
</button>
<button className='rounded bg-gray-300 px-4 py-2'>Wishlist</button>
</ProductCard.Actions>
</ProductCard>
)
}
Benefits of the Compound Component Pattern
- Encapsulation: Keeps related components together and reduces prop drilling.
- Flexibility: Users can arrange subcomponents in any order.
- Reusability: Each subcomponent can be used independently.
Conclusion
The compound component pattern is a clean and scalable way to build reusable UI components in React. By implementing it in our Product Card, we created a flexible API while keeping our components well-structured and maintainable.
Let me know in the comments if you have any questions or improvements!