Back to posts
Compound Components

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!