[{"data":1,"prerenderedAt":130},["ShallowReactive",2],{"qa-\u002Freact\u002Fpatterns\u002Fcompound-components":3},{"page":4,"siblings":110,"blog":127},{"id":5,"title":6,"body":7,"description":11,"difficulty":14,"extension":15,"framework":16,"frameworkSlug":17,"meta":18,"navigation":19,"order":20,"path":21,"questions":22,"questionsCount":101,"related":102,"seo":103,"seoDescription":104,"stem":105,"subtopic":6,"topic":106,"topicSlug":107,"updated":108,"__hash__":109},"qa\u002Freact\u002Fpatterns\u002Fcompound-components.md","Compound Components",{"type":8,"value":9,"toc":10},"minimark",[],{"title":11,"searchDepth":12,"depth":12,"links":13},"",2,[],"medium","md","React","react",{},true,1,"\u002Freact\u002Fpatterns\u002Fcompound-components",[23,28,32,36,40,44,48,52,56,60,64,68,72,76,81,85,89,93,97],{"id":24,"difficulty":25,"q":26,"a":27},"what-are-compound-components","easy","What are compound components and what problem do they solve?","**Compound components** are a set of components that work together to form a cohesive UI\nunit while sharing state implicitly. The parent component owns the state; child\nsub-components consume it without the consumer having to wire it up manually.\n\nThe problem they solve is **prop explosion** on monolithic components. Consider a `\u003CTabs>`\ncomponent — without the pattern you end up passing `tabs`, `activeTab`, `onTabChange`,\n`renderPanel`, and more as props. With compound components the API becomes declarative:\n\n```jsx\n\u002F\u002F Monolithic: consumer must juggle every prop\n\u003CTabs\n  tabs={[{ label: 'A', content: \u003Cdiv \u002F> }]}\n  activeTab={active}\n  onTabChange={setActive}\n\u002F>\n\n\u002F\u002F Compound: consumer composes sub-components freely\n\u003CTabs defaultValue=\"a\">\n  \u003CTabs.List>\n    \u003CTabs.Tab value=\"a\">Tab A\u003C\u002FTabs.Tab>\n    \u003CTabs.Tab value=\"b\">Tab B\u003C\u002FTabs.Tab>\n  \u003C\u002FTabs.List>\n  \u003CTabs.Panel value=\"a\">Content A\u003C\u002FTabs.Panel>\n  \u003CTabs.Panel value=\"b\">Content B\u003C\u002FTabs.Panel>\n\u003C\u002FTabs>\n```\n\n**Rule of thumb:** Reach for compound components when multiple tightly related pieces of\nUI need to share state but the caller should control their layout and composition.\n",{"id":29,"difficulty":25,"q":30,"a":31},"context-based-state-sharing","How do compound sub-components share state without prop drilling?","The standard approach is a **dedicated Context** created inside the compound component\nmodule. The parent component provides the shared state; sub-components consume it via\n`useContext`. This keeps the shared state invisible to the consumer of the component.\n\n```jsx\n\u002F\u002F 1. Create context (not exported — internal implementation detail)\nconst TabsContext = createContext(null);\n\n\u002F\u002F 2. Parent provides state\nfunction Tabs({ defaultValue, children }) {\n  const [active, setActive] = useState(defaultValue);\n  return (\n    \u003CTabsContext.Provider value={{ active, setActive }}>\n      {children}\n    \u003C\u002FTabsContext.Provider>\n  );\n}\n\n\u002F\u002F 3. Sub-components consume without any extra props from caller\nfunction TabsTab({ value, children }) {\n  const { active, setActive } = useContext(TabsContext);\n  return (\n    \u003Cbutton\n      aria-selected={active === value}\n      onClick={() => setActive(value)}\n    >\n      {children}\n    \u003C\u002Fbutton>\n  );\n}\n```\n\n**Rule of thumb:** Keep the Context object private to the module; export only the\nsub-components so consumers never reach into internal state directly.\n",{"id":33,"difficulty":25,"q":34,"a":35},"dot-notation-api","What is the dot-notation API pattern and how do you set it up?","**Dot-notation** attaches sub-components as static properties on the parent component\n(`Menu.Item`, `Tabs.Panel`). This groups related components under a single import and\nmakes the relationship obvious in JSX.\n\n```jsx\n\u002F\u002F Attach sub-components as static properties\nfunction Tabs({ defaultValue, children }) { \u002F* ... *\u002F }\n\nTabs.List  = function TabsList({ children }) { \u002F* ... *\u002F };\nTabs.Tab   = function TabsTab({ value, children }) { \u002F* ... *\u002F };\nTabs.Panel = function TabsPanel({ value, children }) { \u002F* ... *\u002F };\n\nexport default Tabs;\n\n\u002F\u002F Consumer imports one name, uses all four components\nimport Tabs from '.\u002FTabs';\n\n\u003CTabs defaultValue=\"a\">\n  \u003CTabs.List>\n    \u003CTabs.Tab value=\"a\">A\u003C\u002FTabs.Tab>\n  \u003C\u002FTabs.List>\n  \u003CTabs.Panel value=\"a\">Content\u003C\u002FTabs.Panel>\n\u003C\u002FTabs>\n```\n\nAn alternative is named exports (`export { Tabs, TabsList, TabsTab, TabsPanel }`) which\nworks well with tree-shaking but loses the visual grouping at the import site.\n\n**Rule of thumb:** Use dot-notation for libraries or shared design system components;\nnamed exports are fine for single-app usage where IDE autocomplete fills the gap.\n",{"id":37,"difficulty":14,"q":38,"a":39},"react-children-traversal","What is the React.Children traversal approach to compound components and what are its drawbacks?","Before Context, a common technique was **`React.Children.map`** with `React.cloneElement`\nto inject props directly into child elements. The parent inspects its children and injects\nthe shared state as additional props.\n\n```jsx\nfunction Tabs({ defaultValue, children }) {\n  const [active, setActive] = useState(defaultValue);\n\n  return (\n    \u003Cdiv>\n      {React.Children.map(children, child => {\n        if (!React.isValidElement(child)) return child;\n        \u002F\u002F Inject active\u002FsetActive into every direct child\n        return React.cloneElement(child, { active, setActive });\n      })}\n    \u003C\u002Fdiv>\n  );\n}\n```\n\nKey drawbacks:\n- **Breaks with nesting** — `cloneElement` only reaches direct children; wrapping a\n  `\u003CTabs.Tab>` in a `\u003Cdiv>` for layout breaks the injection chain.\n- **Type safety is lost** — injected props are not reflected in the child component's\n  declared prop types.\n- **Performance** — clones every child on every render even if state did not change.\n- **Fragile** — relies on element identity checks that break with HOCs or memoized\n  wrappers.\n\n**Rule of thumb:** Prefer Context over `cloneElement` for new compound components;\n`cloneElement` is a legacy pattern kept alive only to understand older codebases.\n",{"id":41,"difficulty":14,"q":42,"a":43},"controlled-vs-uncontrolled-compound","How do you make a compound component support both controlled and uncontrolled modes?","Follow the same **controlled\u002Funcontrolled duality** used by HTML inputs: accept an\noptional `value` + `onChange` for controlled mode, and a `defaultValue` for uncontrolled.\nInside the component, maintain internal state only when the caller passes no `value`.\n\n```jsx\nfunction Tabs({ value, defaultValue, onChange, children }) {\n  \u002F\u002F Internal state only used in uncontrolled mode\n  const [internalActive, setInternalActive] = useState(defaultValue ?? null);\n\n  \u002F\u002F Controlled if `value` prop is provided\n  const isControlled = value !== undefined;\n  const active = isControlled ? value : internalActive;\n\n  function handleChange(next) {\n    if (!isControlled) setInternalActive(next); \u002F\u002F update internal state\n    onChange?.(next);                            \u002F\u002F always notify caller\n  }\n\n  return (\n    \u003CTabsContext.Provider value={{ active, handleChange }}>\n      {children}\n    \u003C\u002FTabsContext.Provider>\n  );\n}\n```\n\n**Rule of thumb:** A component is controlled when the caller owns the value prop; never\nswitch between modes at runtime — log a warning if `value` goes from defined to undefined.\n",{"id":45,"difficulty":14,"q":46,"a":47},"implicit-vs-explicit-state","What is the difference between implicit and explicit state sharing in compound components?","**Implicit sharing** (via Context) hides the wiring from the consumer. Sub-components\nreach into Context themselves — the consumer never passes state between siblings.\n\n**Explicit sharing** means the consumer manually threads state as props, typically using\n`render props` or a `children` function pattern:\n\n```jsx\n\u002F\u002F Explicit — caller owns and passes state\n\u003CTabs>\n  {({ active, setActive }) => (\n    \u003C>\n      \u003CTabList active={active} onSelect={setActive} \u002F>\n      \u003CTabPanel active={active} \u002F>\n    \u003C\u002F>\n  )}\n\u003C\u002FTabs>\n\n\u002F\u002F Implicit — Context handles the wiring\n\u003CTabs defaultValue=\"a\">\n  \u003CTabs.List>\n    \u003CTabs.Tab value=\"a\">A\u003C\u002FTabs.Tab>  {\u002F* reads Context internally *\u002F}\n  \u003C\u002FTabs.List>\n  \u003CTabs.Panel value=\"a\">Content\u003C\u002FTabs.Panel>\n\u003C\u002FTabs>\n```\n\nImplicit sharing produces cleaner JSX and is the hallmark of the compound components\npattern. Explicit sharing via render props is more flexible (state is available anywhere\nin the subtree) but transfers more complexity to the consumer.\n\n**Rule of thumb:** Use implicit Context sharing for encapsulated design-system components;\nuse render props when the consumer genuinely needs access to internal state for custom logic.\n",{"id":49,"difficulty":25,"q":50,"a":51},"custom-hook-for-context","Why and how do you wrap a compound component's Context in a custom hook?","Wrapping the Context in a **custom hook** adds a guard that throws a descriptive error\nwhen a sub-component is used outside its parent. This is far easier to debug than the\nsilent `null` that `useContext` returns for a missing provider.\n\n```jsx\nconst TabsContext = createContext(null);\n\n\u002F\u002F Custom hook with guard\nfunction useTabs() {\n  const ctx = useContext(TabsContext);\n  if (!ctx) {\n    throw new Error(\n      'useTabs must be used within a \u003CTabs> component'\n    );\n  }\n  return ctx;\n}\n\n\u002F\u002F Sub-components use the guarded hook, not useContext directly\nfunction TabsTab({ value, children }) {\n  const { active, handleChange } = useTabs(); \u002F\u002F throws if misused\n  return (\n    \u003Cbutton onClick={() => handleChange(value)}>\n      {children}\n    \u003C\u002Fbutton>\n  );\n}\n```\n\n**Rule of thumb:** Always export the custom hook rather than the Context object; this\nprevents consumers from bypassing the guard and simplifies future refactors.\n",{"id":53,"difficulty":14,"q":54,"a":55},"typescript-typing","How do you type a compound component API with TypeScript?","You need to type three things: the **Context shape**, the **sub-component props**, and\nthe **parent component augmented with its sub-component statics**.\n\n```tsx\n\u002F\u002F 1. Type the shared context\ninterface TabsContextValue {\n  active: string;\n  handleChange: (value: string) => void;\n}\nconst TabsContext = createContext\u003CTabsContextValue | null>(null);\n\n\u002F\u002F 2. Type each sub-component normally\ninterface TabsTabProps {\n  value: string;\n  children: React.ReactNode;\n}\nfunction TabsTab({ value, children }: TabsTabProps) { \u002F* ... *\u002F }\n\n\u002F\u002F 3. Augment the parent type with static properties\ninterface TabsComponent extends React.FC\u003CTabsProps> {\n  List:  React.FC\u003C{ children: React.ReactNode }>;\n  Tab:   React.FC\u003CTabsTabProps>;\n  Panel: React.FC\u003C{ value: string; children: React.ReactNode }>;\n}\n\nconst Tabs = function Tabs({ defaultValue, children }: TabsProps) {\n  \u002F* ... *\u002F\n} as TabsComponent;\n\nTabs.List  = TabsList;\nTabs.Tab   = TabsTab;\nTabs.Panel = TabsPanel;\n```\n\n**Rule of thumb:** Use a named interface that extends `React.FC\u003CProps>` to attach\nsub-component types; this gives consumers full autocomplete on `\u003CTabs.` in the editor.\n",{"id":57,"difficulty":25,"q":58,"a":59},"real-world-accordion","Describe the compound component structure for an Accordion.","An **Accordion** is a natural fit: the root tracks which item(s) are expanded; each\n`Accordion.Item` provides a nested sub-Context for its own `value`; `Accordion.Trigger`\ntoggles expansion; `Accordion.Content` conditionally renders.\n\n```jsx\n\u002F\u002F Root: tracks open items\nfunction Accordion({ type = 'single', children }) {\n  const [open, setOpen] = useState(new Set());\n  function toggle(value) {\n    setOpen(prev => {\n      const next = new Set(type === 'single' ? [] : prev);\n      prev.has(value) ? next.delete(value) : next.add(value);\n      return next;\n    });\n  }\n  return (\n    \u003CAccordionContext.Provider value={{ open, toggle }}>\n      {children}\n    \u003C\u002FAccordionContext.Provider>\n  );\n}\n\n\u002F\u002F Item: provides its own value to children via a second context\nAccordion.Item = function AccordionItem({ value, children }) {\n  return (\n    \u003CAccordionItemContext.Provider value={value}>\n      {children}\n    \u003C\u002FAccordionItemContext.Provider>\n  );\n};\n\n\u002F\u002F Trigger reads item context to know which value to toggle\nAccordion.Trigger = function AccordionTrigger({ children }) {\n  const value  = useContext(AccordionItemContext);\n  const { open, toggle } = useAccordion();\n  return (\n    \u003Cbutton aria-expanded={open.has(value)} onClick={() => toggle(value)}>\n      {children}\n    \u003C\u002Fbutton>\n  );\n};\n```\n\n**Rule of thumb:** Nested Context layers are fine — use one for the collection-level\nstate and one for each item's identity; keep both Context objects private.\n",{"id":61,"difficulty":14,"q":62,"a":63},"flexible-composition","How does the compound component pattern enable flexible composition that monolithic components cannot?","With a monolithic component the **layout is fixed** — the component renders its own\nmarkup structure. With compound components the **consumer controls the layout** by placing\nsub-components anywhere in JSX, interspersing other elements freely.\n\n```jsx\n\u002F\u002F Monolithic: layout is baked in, no way to add a badge next to a tab label\n\u003CTabs tabs={[{ label: 'A' }, { label: 'B' }]} \u002F>\n\n\u002F\u002F Compound: consumer inserts arbitrary content between or inside sub-components\n\u003CTabs defaultValue=\"a\">\n  \u003Cheader className=\"flex justify-between\">\n    \u003CTabs.List>\n      \u003CTabs.Tab value=\"a\">\n        Dashboard \u003CBadge count={3} \u002F> {\u002F* freely inserted *\u002F}\n      \u003C\u002FTabs.Tab>\n      \u003CTabs.Tab value=\"b\">Settings\u003C\u002FTabs.Tab>\n    \u003C\u002FTabs.List>\n    \u003CUserMenu \u002F>  {\u002F* unrelated element, same row *\u002F}\n  \u003C\u002Fheader>\n\n  \u003CTabs.Panel value=\"a\">\u003CDashboardContent \u002F>\u003C\u002FTabs.Panel>\n  \u003CTabs.Panel value=\"b\">\u003CSettingsContent \u002F>\u003C\u002FTabs.Panel>\n\u003C\u002FTabs>\n```\n\n**Rule of thumb:** If different callers need different layouts or want to inject elements\nbetween parts of a component, compound components are the right abstraction; if every\ncaller wants the same layout, a simpler prop-driven component is sufficient.\n",{"id":65,"difficulty":25,"q":66,"a":67},"when-not-to-use","When should you NOT use the compound component pattern?","Compound components add indirection and a module-level Context. They are the wrong choice\nwhen:\n\n- **The component is simple** — a `\u003CButton>` with a loading state does not warrant a\n  compound API; a single `loading` boolean prop is clearer.\n- **Sub-components are always rendered together** — if callers never need to rearrange the\n  parts, the flexibility is overhead with no benefit.\n- **Server components** — React Server Components cannot use Context, so the pattern\n  requires pushing the compound root to a client boundary.\n- **Over-engineering** — a team unfamiliar with the pattern will find it harder to\n  maintain than a straightforward props API.\n\n```jsx\n\u002F\u002F Overkill — compound component for something trivially prop-driven\n\u003CButton.Root>\n  \u003CButton.Icon name=\"save\" \u002F>\n  \u003CButton.Label>Save\u003C\u002FButton.Label>\n\u003C\u002FButton.Root>\n\n\u002F\u002F Just use props\n\u003CButton icon=\"save\" loading={saving}>Save\u003C\u002FButton>\n```\n\n**Rule of thumb:** Use compound components when callers regularly need layout control\nover multiple coordinated sub-pieces; prefer simple props when one \"shape\" serves everyone.\n",{"id":69,"difficulty":14,"q":70,"a":71},"select-compound-example","Walk through a minimal compound component implementation of a custom Select.","A custom **Select** needs: the root to track open\u002Fvalue state; a `Trigger` to open the\ndropdown; a `List` to contain options; and `Option` items that close the list on selection.\n\n```jsx\nconst SelectCtx = createContext(null);\nfunction useSelect() {\n  const ctx = useContext(SelectCtx);\n  if (!ctx) throw new Error('Must be inside \u003CSelect>');\n  return ctx;\n}\n\nfunction Select({ value, onChange, children }) {\n  const [open, setOpen] = useState(false);\n  return (\n    \u003CSelectCtx.Provider value={{ value, onChange, open, setOpen }}>\n      \u003Cdiv className=\"relative\">{children}\u003C\u002Fdiv>\n    \u003C\u002FSelectCtx.Provider>\n  );\n}\n\nSelect.Trigger = function SelectTrigger({ children }) {\n  const { value, open, setOpen } = useSelect();\n  return (\n    \u003Cbutton onClick={() => setOpen(o => !o)} aria-haspopup=\"listbox\">\n      {value ?? children}  {\u002F* show selected value or placeholder *\u002F}\n    \u003C\u002Fbutton>\n  );\n};\n\nSelect.Option = function SelectOption({ value, children }) {\n  const { onChange, setOpen } = useSelect();\n  return (\n    \u003Cli\n      role=\"option\"\n      onClick={() => { onChange(value); setOpen(false); }}\n    >\n      {children}\n    \u003C\u002Fli>\n  );\n};\n```\n\n**Rule of thumb:** For accessible custom selects, layer the compound pattern on top of\n`aria-*` attributes rather than replacing native `\u003Cselect>` unless custom styling\ngenuinely requires it.\n",{"id":73,"difficulty":14,"q":74,"a":75},"menu-compound-example","How would you implement a compound Menu component with keyboard navigation?","A **Menu** follows the same Context pattern but adds `useRef` and keyboard event handling\nto support arrow-key navigation and focus management.\n\n```jsx\nconst MenuCtx = createContext(null);\n\nfunction Menu({ children }) {\n  const [open, setOpen]       = useState(false);\n  const [focused, setFocused] = useState(0);\n  const itemRefs              = useRef([]);\n\n  function handleKeyDown(e) {\n    if (e.key === 'ArrowDown') {\n      const next = (focused + 1) % itemRefs.current.length;\n      setFocused(next);\n      itemRefs.current[next]?.focus(); \u002F\u002F move DOM focus\n    }\n    if (e.key === 'Escape') setOpen(false);\n  }\n\n  return (\n    \u003CMenuCtx.Provider value={{ open, setOpen, focused, setFocused, itemRefs }}>\n      \u003Cdiv onKeyDown={handleKeyDown}>{children}\u003C\u002Fdiv>\n    \u003C\u002FMenuCtx.Provider>\n  );\n}\n\nMenu.Item = function MenuItem({ index, onSelect, children }) {\n  const { itemRefs } = useContext(MenuCtx);\n  return (\n    \u003Cbutton\n      ref={el => (itemRefs.current[index] = el)} \u002F\u002F register ref\n      role=\"menuitem\"\n      tabIndex={-1}\n      onClick={onSelect}\n    >\n      {children}\n    \u003C\u002Fbutton>\n  );\n};\n```\n\n**Rule of thumb:** Store an array of item refs in the root Context so the root's\n`keyDown` handler can programmatically focus items without each item knowing about\nits siblings.\n",{"id":77,"difficulty":78,"q":79,"a":80},"default-context-value","hard","What should you pass as the default value to createContext in a compound component and why?","The **default Context value** is used only when a component renders outside any matching\n`Provider` — which for compound components means the sub-component is being used\nincorrectly. There are two schools of thought:\n\n1. **`null` + runtime guard** (recommended): pass `null` as the default and throw in the\n   custom hook. This surfaces misuse as a clear error rather than a silent `undefined`.\n\n2. **Typed fallback**: provide a full default shape. This prevents errors but silently\n   allows misuse, making bugs hard to trace.\n\n```tsx\n\u002F\u002F Option 1: null default + guard (preferred)\nconst TabsCtx = createContext\u003CTabsContextValue | null>(null);\n\nfunction useTabs(): TabsContextValue {\n  const ctx = useContext(TabsCtx);\n  if (ctx === null) {\n    throw new Error('\u003CTabs.Tab> must be rendered inside \u003CTabs>');\n  }\n  return ctx;\n}\n\n\u002F\u002F Option 2: full default (use only for optional composition)\nconst TabsCtx = createContext\u003CTabsContextValue>({\n  active: '',\n  handleChange: () => {},  \u002F\u002F no-op silently\n});\n```\n\n**Rule of thumb:** Use `null` with a guard for required compound parents; use a no-op\ndefault only when a sub-component is genuinely optional (e.g., a tooltip that renders\nstandalone without a parent wrapper).\n",{"id":82,"difficulty":14,"q":83,"a":84},"testing-compound-components","How do you test compound components with React Testing Library?","Test compound components **through their full composed API** — render the parent with\nsub-components just as a consumer would. Avoid importing and rendering sub-components\nin isolation because they depend on the Context being present.\n\n```jsx\nimport { render, screen } from '@testing-library\u002Freact';\nimport userEvent from '@testing-library\u002Fuser-event';\nimport Tabs from '.\u002FTabs';\n\ntest('switches active panel on tab click', async () => {\n  render(\n    \u003CTabs defaultValue=\"a\">\n      \u003CTabs.List>\n        \u003CTabs.Tab value=\"a\">Tab A\u003C\u002FTabs.Tab>\n        \u003CTabs.Tab value=\"b\">Tab B\u003C\u002FTabs.Tab>\n      \u003C\u002FTabs.List>\n      \u003CTabs.Panel value=\"a\">Content A\u003C\u002FTabs.Panel>\n      \u003CTabs.Panel value=\"b\">Content B\u003C\u002FTabs.Panel>\n    \u003C\u002FTabs>\n  );\n\n  \u002F\u002F Initial state\n  expect(screen.getByText('Content A')).toBeVisible();\n  expect(screen.queryByText('Content B')).not.toBeVisible();\n\n  \u002F\u002F Interact and assert\n  await userEvent.click(screen.getByRole('button', { name: 'Tab B' }));\n  expect(screen.getByText('Content B')).toBeVisible();\n});\n```\n\nFor controlled compound components, test that `onChange` is called with the correct\nvalue and that the displayed state matches the controlled `value` prop.\n\n**Rule of thumb:** Test behavior (what the user sees and can do), not implementation\n(which Context value changed); this keeps tests resilient to internal refactors.\n",{"id":86,"difficulty":78,"q":87,"a":88},"performance-concerns","What performance concerns exist with Context-based compound components and how do you address them?","Every consumer of a Context re-renders whenever the **entire Context value changes**.\nIf the parent passes a new object reference on every render (even if the values are the\nsame), all sub-components re-render unnecessarily.\n\n```jsx\n\u002F\u002F Problem: new object reference on every render\nfunction Tabs({ children }) {\n  const [active, setActive] = useState('a');\n  return (\n    \u003CTabsCtx.Provider value={{ active, setActive }}> {\u002F* new ref each time *\u002F}\n      {children}\n    \u003C\u002FTabsCtx.Provider>\n  );\n}\n\n\u002F\u002F Fix 1: useMemo to stabilize the context object\nfunction Tabs({ children }) {\n  const [active, setActive] = useState('a');\n  const ctx = useMemo(() => ({ active, setActive }), [active]);\n  return \u003CTabsCtx.Provider value={ctx}>{children}\u003C\u002FTabsCtx.Provider>;\n}\n\n\u002F\u002F Fix 2: split into separate contexts (state vs dispatch)\nconst TabsStateCtx    = createContext(null); \u002F\u002F triggers re-render on change\nconst TabsDispatchCtx = createContext(null); \u002F\u002F stable — setActive never changes\n```\n\nSplitting into a state context and a dispatch context is the most effective approach:\ncomponents that only call `setActive` (like `Tabs.Tab`) subscribe to `TabsDispatchCtx`\nand never re-render when the active value changes.\n\n**Rule of thumb:** Start with a single merged Context; split into state + dispatch only\nwhen profiling confirms unnecessary re-renders are a real bottleneck.\n",{"id":90,"difficulty":78,"q":91,"a":92},"server-components-limitation","How does the React Server Components model affect compound components that use Context?","**React Server Components (RSC)** cannot use Context — `createContext` and `useContext`\nare client-only APIs. This means compound components that rely on Context must be\n**client components** (marked `'use client'`).\n\nThe practical implication is a **client boundary** at the compound component root:\n\n```tsx\n\u002F\u002F tabs.tsx — must be a client component\n'use client';\nimport { createContext, useContext, useState } from 'react';\n\u002F\u002F ... full compound component implementation\n\n\u002F\u002F page.tsx — server component; Tabs forces a client subtree\nimport Tabs from '.\u002Ftabs';\n\nexport default function Page() {\n  return (\n    \u002F\u002F Everything inside \u003CTabs> runs on the client\n    \u003CTabs defaultValue=\"a\">\n      \u003CTabs.Tab value=\"a\">A\u003C\u002FTabs.Tab>\n      \u003CTabs.Panel value=\"a\">\n        {\u002F* Server components can be passed as children to client components *\u002F}\n        \u003CServerDataTable \u002F>\n      \u003C\u002FTabs.Panel>\n    \u003C\u002FTabs>\n  );\n}\n```\n\nYou can still pass **server-rendered content as children** into a client compound\ncomponent — RSC allows server components to be children of client components. The\nserver component renders its output and passes it as serialized props.\n\n**Rule of thumb:** Mark the compound root (and sub-components that use Context) as\n`'use client'`; keep the data-fetching server components as their consumers' children\nrather than embedding them inside the compound component module.\n",{"id":94,"difficulty":14,"q":95,"a":96},"vs-render-props","How do compound components compare to render props as a composition pattern?","Both patterns share state with consumers, but they differ in where the consumer\ninteracts with that state.\n\n**Render props** — the consumer receives state as function arguments and can use it\nanywhere within the callback, including conditional logic. More explicit and flexible,\nbut produces deeply indented JSX (\"callback hell\").\n\n**Compound components** — state is accessed implicitly inside sub-components via Context.\nProduces flat, declarative JSX that reads like HTML.\n\n```jsx\n\u002F\u002F Render props: state is explicit, layout is free-form\n\u003CTabs>\n  {({ active, setActive }) => (\n    \u003Cdiv className=\"custom-layout\">\n      \u003Cbutton onClick={() => setActive('a')}\n        style={{ fontWeight: active === 'a' ? 'bold' : 'normal' }}>A\u003C\u002Fbutton>\n      {active === 'a' && \u003Cdiv>Panel A\u003C\u002Fdiv>}\n    \u003C\u002Fdiv>\n  )}\n\u003C\u002FTabs>\n\n\u002F\u002F Compound: declarative, less flexible but cleaner\n\u003CTabs defaultValue=\"a\">\n  \u003CTabs.Tab value=\"a\">A\u003C\u002FTabs.Tab>\n  \u003CTabs.Panel value=\"a\">Panel A\u003C\u002FTabs.Panel>\n\u003C\u002FTabs>\n```\n\n**Rule of thumb:** Prefer compound components for design-system UI widgets where layout\nflexibility is bounded; prefer render props when consumers need the raw state to drive\narbitrary custom logic.\n",{"id":98,"difficulty":78,"q":99,"a":100},"accessibility-in-compound","How do you handle ARIA attributes in compound components to ensure accessibility?","Compound components should **generate and wire ARIA attributes automatically** so\nconsumers don't need to add them manually. The Context provides stable IDs and\nexpanded\u002Fselected state that sub-components apply to their DOM elements.\n\n```jsx\nfunction Tabs({ defaultValue, id: baseId = useId(), children }) {\n  const [active, setActive] = useState(defaultValue);\n  return (\n    \u003CTabsCtx.Provider value={{ active, setActive, baseId }}>\n      {children}\n    \u003C\u002FTabsCtx.Provider>\n  );\n}\n\nTabs.Tab = function TabsTab({ value, children }) {\n  const { active, setActive, baseId } = useTabs();\n  return (\n    \u003Cbutton\n      id={`${baseId}-tab-${value}`}       \u002F\u002F stable ID\n      role=\"tab\"\n      aria-selected={active === value}\n      aria-controls={`${baseId}-panel-${value}`} \u002F\u002F links to panel\n      onClick={() => setActive(value)}\n    >\n      {children}\n    \u003C\u002Fbutton>\n  );\n};\n\nTabs.Panel = function TabsPanel({ value, children }) {\n  const { active, baseId } = useTabs();\n  return (\n    \u003Cdiv\n      id={`${baseId}-panel-${value}`}     \u002F\u002F matches aria-controls\n      role=\"tabpanel\"\n      aria-labelledby={`${baseId}-tab-${value}`}\n      hidden={active !== value}\n    >\n      {children}\n    \u003C\u002Fdiv>\n  );\n};\n```\n\n**Rule of thumb:** Generate IDs with `useId()` inside the root component and share them\nvia Context so `aria-controls`\u002F`aria-labelledby` pairs are always consistent without\nrequiring consumers to manage IDs manually.\n",19,null,{"description":11},"React compound components interview questions — implicit state sharing, Context-based APIs, flexible composition, and component slot patterns.","react\u002Fpatterns\u002Fcompound-components","Patterns","patterns","2026-06-24","TLvrPCxxzntq3ZbMDXCJk4oBf_smSWVPIoVgZQ_K2G0",[111,112,115,119,123],{"subtopic":6,"path":21,"order":20},{"subtopic":113,"path":114,"order":12},"Render Props & HOCs","\u002Freact\u002Fpatterns\u002Frender-props-hoc",{"subtopic":116,"path":117,"order":118},"Error Boundaries","\u002Freact\u002Fpatterns\u002Ferror-boundaries",3,{"subtopic":120,"path":121,"order":122},"Portals & Refs","\u002Freact\u002Fpatterns\u002Fportals-refs",4,{"subtopic":124,"path":125,"order":126},"forwardRef & useImperativeHandle","\u002Freact\u002Fpatterns\u002Fforward-ref-imperative",5,{"path":128,"title":129},"\u002Fblog\u002Freact-compound-components-guide","React Compound Components — Complete Interview Guide",1782244101274]