Compound Components
A React composition pattern expanded from advanced-react. A compound
component exposes a parent and subcomponents that share implicit state via
Context, instead of taking dozens of props.
The problem it solves
A “monolithic” API with many props (<Select options labelKey valueKey renderOption ... />) is rigid and hard to extend. The compound component flips
that: the parent owns the state, the children wire into it.
<Tabs defaultId="a">
<Tabs.Tab id="a">First</Tabs.Tab>
<Tabs.Tab id="b">Second</Tabs.Tab>
</Tabs>
How it works
- The parent creates a
Contextand holds the shared state (memoizingvalue). - Subcomponents read that
Contextvia an internal hook. - Children are attached as static properties of the parent (
Tabs.Tab = Tab).
const TabsCtx = createContext<{ active: string; set: (id: string) => void } | null>(
null,
);
function useTabs() {
const ctx = useContext(TabsCtx);
if (!ctx) throw new Error("Tabs.Tab must be used inside <Tabs>");
return ctx;
}
function Tabs({ defaultId, children }: { defaultId: string; children: ReactNode }) {
const [active, set] = useState(defaultId);
const value = useMemo(() => ({ active, set }), [active]); // stable identity
return <TabsCtx.Provider value={value}>{children}</TabsCtx.Provider>;
}
function Tab({ id, children }: { id: string; children: ReactNode }) {
const { active, set } = useTabs();
return (
<button aria-selected={active === id} onClick={() => set(id)}>
{children}
</button>
);
}
Tabs.Tab = Tab;
When to use (and when not)
- ✅ Great for components with coordinated parts:
Select,Tabs,Accordion,Menu, composed forms. - ⚠️ The internal hook should throw when used outside the parent — fail early and clearly.
- ⚠️ To pass data deep without a fixed hierarchy, a plain
Contextmay be enough; not everything needs subcomponents.
Interview questions (practice)
- Why memoize the
Contextvaluein the parent? - How do you type
Tabs.Tabwhile keeping consumer autocomplete? - Compound components vs render props — when each?
Related: advanced-react · performance and Context.