Compound Components Pattern

Compound components are a pattern where several related components work together as one cohesive unit, sharing state implicitly through a common context instead of through props passed at every level — like with its and children. The result is a flexible, declarative API for the people who use your component.

Learn Compound Components Pattern in our free React course — an interactive lesson with runnable examples, a practice exercise and a quick recall.

Part of the free React course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

1️⃣ The Shared State Is the Whole Idea

A compound component centers on one piece of shared state — for tabs, "which tab is active." Each sub-component reads that state to render itself and calls a setter to change it. Crucially, the children don't receive this through props; they reach into a shared context, so consumers can nest and arrange them freely.

2️⃣ Build It With a Context Provider

The parent owns the state and wraps its children in a Provider. Sub-components call useContext to read the active id and the setter. No props are threaded through intermediate elements — that's the "implicit" sharing.

3️⃣ The Panel and Misuse Guards

A renders its children only when its id matches the active tab. To keep the API safe, default the context to null and throw a clear error if a sub-component is used outside , so mistakes fail loudly.

These lines build the Tabs parent that provides shared state. Put them in the right order:

📋 Quick Reference

1. How do sub-components share state without prop drilling?

Through a context: the parent renders a Provider, and each sub-component reads the value with useContext .

2. What's the benefit over one big config-prop component?

A flexible, declarative API: consumers arrange the sub-components freely and React wires up the shared behavior.

3. How do you stop a sub-component being used outside the parent?

Default the context to null and throw a clear error in the consumer hook when it's null.

Model an accordion where only one panel is open, sharing the open id through a context object the panels read. Run it and check your output.

Practice quiz

What defines a set of 'compound components'?

  • Components that all live in one file
  • Components that take no props
  • Separate components designed to work together as one unit, sharing state implicitly
  • Components rendered with portals

Answer: Separate components designed to work together as one unit, sharing state implicitly. Like <Tabs>, <Tab>, <TabPanel>, each is its own component but they cooperate through shared state.

How do compound sub-components share state without prop drilling?

  • The parent provides a context and each sub-component reads it with useContext
  • Through global variables
  • By passing props through every level
  • Through localStorage

Answer: The parent provides a context and each sub-component reads it with useContext. The parent renders a context Provider; sub-components call useContext to read or update the shared state.

Where does the shared state (e.g. which tab is active) live?

  • In each individual sub-component
  • In a separate Redux store only
  • In the DOM
  • In the parent component that renders the Provider

Answer: In the parent component that renders the Provider. The parent owns the state with useState and supplies it via the Provider's value.

What goes on the context's Provider so children can read it?

  • A children prop
  • A value prop holding the shared state
  • A ref prop
  • A key prop

Answer: A value prop holding the shared state. <Ctx.Provider value={{ active, setActive }}> supplies the shared state to all consumers below it.

How do you stop a sub-component being used outside its parent?

  • Default the context to null and throw a clear error if useContext returns null
  • Wrap it in try/catch
  • Use a default prop
  • Disable the component in production

Answer: Default the context to null and throw a clear error if useContext returns null. A guard hook throws a helpful error when context is null, so <Tab> used outside <Tabs> fails loudly.

Why is the compound pattern better than one big config-prop component?

  • It runs without React
  • It avoids using context
  • It gives consumers a flexible, declarative API they arrange freely
  • It removes the need for keys

Answer: It gives consumers a flexible, declarative API they arrange freely. Consumers nest sub-components however they like and React wires up the shared behavior — far more flexible.

What does a <TabPanel> typically do?

  • Always render its children
  • Render its children only when its id matches the active tab
  • Set the active tab on mount
  • Render the parent again

Answer: Render its children only when its id matches the active tab. TabPanel reads the active id from context and renders its children only when active === id, else null.

A new value object is created on every parent render. What problem can that cause?

  • The app crashes
  • The context becomes null
  • Children lose their keys
  • All context consumers re-render unnecessarily

Answer: All context consumers re-render unnecessarily. A fresh value each render makes every consumer re-render; memoize the value with useMemo to avoid it.

Which hook does a sub-component call to read the shared state?

  • useState
  • useContext
  • useRef
  • useReducer

Answer: useContext. Sub-components call useContext(TabsCtx) to read the active id and setter the parent provided.

Where should createContext(null) be called for a compound component?

  • Inside each sub-component's render
  • Inside an effect
  • Once at module scope so all sub-components share the same context
  • On every click

Answer: Once at module scope so all sub-components share the same context. Create the context once at module scope so every sub-component references the same shared channel.