eduardweb.
Hooks & PatternsAvansat#typescript#react#design-system#compound-components

Pattern-ul Compound Components: Cum am rescris un Accordion rigid într-un API stil shadcn

De Ana Ionescu, 26 iul. 2026 · 8 vizualizări · 3 like-uri

Postat 26 iul. 2026
typescript
import React, { createContext, useContext, useState, ReactNode } from "react";

type AccordionContextType = {
  openItem: string | null;
  toggle: (id: string) => void;
};

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

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

  return (
    <AccordionContext.Provider value={{ openItem, toggle }}>
      <div className="border rounded-lg divide-y">{children}</div>
    </AccordionContext.Provider>
  );
}

export function useAccordion() {
  const context = useContext(AccordionContext);
  if (!context) throw new Error("Trebuie folosit în interiorul <Accordion>");
  return context;
}

Salutare. La ultimul refactoring pe un SaaS cu peste 12k de useri activi, m-am izbit de clasica problemă a componentelor monolitice. Aveam un <Accordion items={items} /> moștenit care ajunsese la peste 15 prop-uri: renderHeader, iconPosition, allowMultiple, customHeaderStyles și alte aberații adunate în doi ani. Fiecare cerință nouă de la echipa de product înseamna încă un if adăugat în componenta de bază.

După ce am pierdut o jumătate de zi încercând să bag un badge și un buton de delete în interiorul header-ului de la un singur item, am zis stop. Am aruncat componenta veche și am rescris-o folosind pattern-ul de Compound Components, exact cum fac cei de la Radix UI sau shadcn/ui.

De ce e greșit pattern-ul bazat pe un array de obiecte

Când pasezi datele sub formă de items={[{ title: '...', content: '...' }]} căzi direct în capcana prop-drilling-ului și pierzi controlul pe HTML. Dintr-o dată, componenta ta trebuie să știe cum se randază fiecare bucățică din DOM. Dacă designerul vrea un icon pe stânga la un item și pe dreapta la altul, ești obligat să adaugi flag-uri în obiectul de date.

Prin Compound Components, expui un ansamblu de componente care lucrează împreună sub capotă, dar lase HTML-ul și layout-ul la latitudinea celui care le folosește.

Cum construim arhitectura cu Context API

Secretul stă într-un React Context invizibil care sincronizează starea între părinte și copii. Părintele <Accordion> ține starea item-ului deschis, iar sub-componentele (AccordionItem, AccordionTrigger, AccordionContent) doar consumă acel context.

Consumul devine extrem de curat și lizibil în cod:

<Accordion>
  <AccordionItem value="item-1">
    <AccordionTrigger>Titlu flexibil cu <Badge>Nou</Badge></AccordionTrigger>
    <AccordionContent>Conținutul exact pe care ți-l dorești.</AccordionContent>
  </AccordionItem>
</Accordion>

Fiecare sub-componentă își extrage starea din custom hook-ul useAccordion(). Dacă AccordionItem are value="item-1" și openItem din context este tot "item-1", atunci <AccordionContent> știe să se afișeze. Fără prop-uri pasate explicit prin 3 niveluri de componente.

Trade-off-uri reale: Flexibilitate vs. Verbostate

Trebuie să fim sinceri, niciun pattern nu e un glonț de argint. Am economisit în jur de 30% din timpul de mentenanță pe design system, dar am dat de următoarele minusuri:

  1. Boilerplate mai mult la utilizare: Pentru un banal Accordion cu două paragrafe de text, scrii 6 tag-uri în loc de unul singur cu un array de date.
  2. Risc de runtime error: Dacă un dev junior pune un <AccordionTrigger> în afara părintelui <Accordion>, aplicația crapă dacă nu pui o gardă explicită în custom hook (if (!context) throw new Error(...)).
  3. Render-uri inutile la state change: Dacă obiectul pasat în AccordionContext.Provider nu e memoizat cu useMemo, toate sub-componentele se vor re-randa la fiecare toggle de tab. La 100 de iteme dintr-o listă mare, se simte lag-ul.

Pentru noi, tranziția la acest pattern a închis definitiv ticket-urile de tipul "mai adaugă un prop pentru un caz special". Arhitectura e predictibilă și super ușor de stilizat cu Tailwind CSS.

Voi ce strategie folosiți pentru componentele complexe de UI în proiectele mari? Rămâneți pe prop-uri configurabile sau le spargeți în compound components?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.