What Is the Compound Components Pattern in React?
Learn the compound components pattern in React — sharing implicit state via context so consumers compose UI pieces flexibly.
Expected Interview Answer
Compound components is a pattern where several components work together to form one cohesive UI unit — like `<Select>`, `<Select.Option>` — sharing implicit state through context so the consumer composes the pieces flexibly while the internal wiring (which option is selected, how they communicate) stays hidden inside the parent.
Instead of a single monolithic component accepting a long list of configuration props (`options`, `renderOption`, `onSelectClass`), the parent component (e.g. `Tabs`) creates and provides shared state via React Context, and child components (`Tabs.List`, `Tabs.Panel`) consume that context to know their role, like which tab is active, without the consumer having to manually wire props between siblings. This mirrors how native HTML elements like `<select>` and `<option>` implicitly work together as one unit. The consumer gets flexibility to reorder, omit, or wrap the sub-components arbitrarily since they are still just regular JSX children, while the parent retains full control over the shared logic and enforces the relationship through context rather than prop drilling. The main tradeoff is that compound components rely on children being rendered inside the correct parent (context must be present), so libraries typically throw a clear error if a sub-component is used outside its expected compound parent.
- Avoids long, unwieldy configuration prop lists on a single monolithic component
- Consumer freely composes, reorders, and styles sub-components as normal JSX
- Shared state stays centralized in context, avoiding manual prop drilling between siblings
- Mirrors familiar native HTML element relationships like select/option
AI Mentor Explanation
Compound components are like a franchise`s dugout, captain`s armband, and scoreboard all silently coordinating through one shared match-state feed instead of the coach manually relaying each fielding change to every single object individually. The captain wears the armband, the scoreboard updates, and the dugout reacts, all reading the same underlying match state without the coach wiring each one by hand. Any of these pieces could be rearranged on the ground without breaking the coordination, since they all just tap into the shared feed. That implicit-shared-state-across-cooperating-pieces design is exactly the compound components pattern.
Step-by-Step Explanation
Step 1
Parent creates shared state and context
The compound parent (e.g. Tabs) holds state like activeIndex and provides it via React Context.
Step 2
Sub-components consume the context
Children like Tabs.List and Tabs.Panel read the shared context to know their role and state.
Step 3
Consumer composes freely
The parent renders whichever sub-components it wants, in any order, as regular JSX children.
Step 4
Parent guards against misuse
Sub-components throw a clear error if rendered outside their expected compound parent context.
What Interviewer Expects
- Clear explanation of implicit shared state via context between cooperating components
- Comparison against a single monolithic component with a long configuration prop list
- Analogy to native HTML relationships like select/option
- Awareness that sub-components require the correct parent context to function
Common Mistakes
- Passing all shared state as explicit props between siblings instead of using context
- Not guarding sub-components against being rendered without the required parent context
- Over-engineering a compound API for a component that never needs that flexibility
- Forgetting to memoize the context value, causing unnecessary re-renders of all consumers
Best Answer (HR Friendly)
“Compound components are a way of building a component like a set of LEGO pieces that all secretly talk to each other — for example, a Tabs component with separate TabList and TabPanel pieces that automatically know which tab is active. Instead of passing a giant list of settings into one component, you compose the pieces however you like, and they coordinate behind the scenes.”
Code Example
const TabsContext = createContext(null)
function Tabs({ children, defaultIndex = 0 }) {
const [activeIndex, setActiveIndex] = useState(defaultIndex)
return (
<TabsContext.Provider value={{ activeIndex, setActiveIndex }}>
{children}
</TabsContext.Provider>
)
}
function Tab({ index, children }) {
const { activeIndex, setActiveIndex } = useContext(TabsContext)
return (
<button onClick={() => setActiveIndex(index)} aria-selected={activeIndex === index}>
{children}
</button>
)
}
Tabs.Tab = Tab
// Usage: <Tabs><Tabs.Tab index={0}>One</Tabs.Tab><Tabs.Tab index={1}>Two</Tabs.Tab></Tabs>Follow-up Questions
- How would you guard Tab against being rendered outside a Tabs parent?
- How does compound components compare to passing an options array as a single prop?
- How would you memoize the context value to avoid unnecessary re-renders?
- How does this pattern relate to how native <select> and <option> elements cooperate?
MCQ Practice
1. How do sub-components in the compound components pattern typically share state?
The parent creates context that child sub-components consume to coordinate implicitly.
2. What is a key advantage of compound components over one monolithic configurable component?
Compound components avoid unwieldy config props by letting the consumer compose pieces directly.
3. What typically happens if a compound sub-component is rendered outside its expected parent?
Since sub-components depend on the parent`s context, good implementations detect and error on misuse.
Flash Cards
What mechanism do compound components use to share state? — React Context provided by the parent component.
What native HTML relationship does this pattern mirror? — select and option elements implicitly working together.
What is the main flexibility benefit? — Consumers can freely reorder/compose sub-components as regular JSX.
What should sub-components do if rendered outside the parent? — Throw a clear error, since the required context is missing.