typescriptadvanced

React Compound Component Pattern

Build flexible compound components using React context for shared state between related parts.

typescript
import { createContext, useContext, useState, ReactNode } from 'react';

// Accordion compound component
interface AccordionContextType {
  activeId: string | null;
  toggle: (id: string) => void;
}

const AccordionContext = createContext<AccordionContextType | null>(null);

function useAccordion() {
  const ctx = useContext(AccordionContext);
  if (!ctx) throw new Error('Must be used within Accordion');
  return ctx;
}

function Accordion({ children }: { children: ReactNode }) {
  const [activeId, setActiveId] = useState<string | null>(null);
  const toggle = (id: string) => setActiveId((prev) => (prev === id ? null : id));

  return (
    <AccordionContext.Provider value={{ activeId, toggle }}>
      <div className="divide-y divide-white/10">{children}</div>
    </AccordionContext.Provider>
  );
}

function AccordionItem({ id, children }: { id: string; children: ReactNode }) {
  return <div data-id={id}>{children}</div>;
}

function AccordionTrigger({ id, children }: { id: string; children: ReactNode }) {
  const { activeId, toggle } = useAccordion();
  return (
    <button
      className="w-full p-4 text-left flex justify-between"
      onClick={() => toggle(id)}
    >
      {children}
      <span>{activeId === id ? '−' : '+'}</span>
    </button>
  );
}

function AccordionContent({ id, children }: { id: string; children: ReactNode }) {
  const { activeId } = useAccordion();
  if (activeId !== id) return null;
  return <div className="p-4 pt-0">{children}</div>;
}

// Attach sub-components
Accordion.Item = AccordionItem;
Accordion.Trigger = AccordionTrigger;
Accordion.Content = AccordionContent;

// Usage
function FAQ() {
  return (
    <Accordion>
      <Accordion.Item id="q1">
        <Accordion.Trigger id="q1">What is React?</Accordion.Trigger>
        <Accordion.Content id="q1">A UI library.</Accordion.Content>
      </Accordion.Item>
      <Accordion.Item id="q2">
        <Accordion.Trigger id="q2">Why use TypeScript?</Accordion.Trigger>
        <Accordion.Content id="q2">Type safety.</Accordion.Content>
      </Accordion.Item>
    </Accordion>
  );
}

Use Cases

  • Building flexible component libraries
  • Accordion, tabs, and dropdown patterns
  • Sharing state between related sub-components

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.