eduardweb.
Hooks & PatternsAvansat#typescript#react#frontend#design-patterns

Pattern-ul Compound Components în React: De ce am renunțat la props monolitice pentru Accordion

De Andreea Crăciun, 13 aug. 2026 · 6 vizualizări · 2 like-uri

Postat acum 4 zile
typescript
import React, { createContext, useContext, useState } from 'react';

type AccordionContextType = {
  activeValue: string | null;
  toggle: (value: string) => void;
};

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

export function Accordion({ children }: { children: React.ReactNode }) {
  const [activeValue, setActiveValue] = useState<string | null>(null);
  const toggle = (val: string) => setActiveValue(prev => (prev === val ? null : val));

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

export function useAccordion() {
  const ctx = useContext(AccordionContext);
  if (!ctx) throw new Error('Sub-componentele Accordion trebuie folosite în interiorul <Accordion>');
  return ctx;
}

Prop drilling-ul la componentele complexe de UI devine rapid un coșmar de mentenanță pe măsură ce aplicația crește. Am pățit-o acum doi ani pe un proiect enterprise cu peste 12k utilizatori activi, când o simplă cerere de a adăuga un badge dinamic și un buton de export în antetul unui accordion ne-a forțat să modificăm 8 props-uri prin 4 niveluri de componente. Soluția curată pentru această problemă este pattern-ul de Compound Components, popularizat puternic de biblioteci precum Radix UI sau shadcn/ui.

De ce pică modelul clasic bazat pe props

Modelul tradițional de a construi un Accordion arată de obicei așa: <Accordion items={data} renderHeader={...} renderContent={...} />. La prima vedere pare o abstracție comodă, dar devine rigidă extrem de repede.

Când designerul îți cere ca al treilea item din listă să aibă un fundal roșu, un tooltip custom pe iconiță și o acțiune diferită la click, interfața componentei explodează. Ajungi să adaugi headerStyle, customIconRenderer, onHeaderItemClick și tot felul de flag-uri booleane până când componenta devine imposibil de citit și testat.

Trebuie să transmiți starea de open/closed peste tot, scriind cod repetitiv care nu aduce nicio valoare de business.

Anatomia arhitecturii cu Context și Slot-uri

Conceptul de Compound Components funcționează exact ca elementele native HTML <select> și <option>. Elementele cooperează implicit în fundal, partajând starea fără ca tu să o pasezi explicit prin atribute.

În React, realizăm asta cu un Context intern ascuns. Componenta părinte (<Accordion>) gestionează valoarea activă și expune o funcție de toggle. Sub-componentele (<AccordionItem>, <AccordionTrigger>, <AccordionContent>) consumă acest context printr-un custom hook intern.

Astfel, codul devine declarativ și extrem de lizibil:

<Accordion type="single">
  <AccordionItem value="item-1">
    <AccordionTrigger>
      <span>Setări Profil</span>
      <Badge>Nou</Badge>
    </AccordionTrigger>
    <AccordionContent>
      <ProfileForm />
    </AccordionContent>
  </AccordionItem>
</Accordion>

Această structură îți oferă libertate totală de stilizare și structurare markup în interiorul fiecărui slot, fără să modifici vreo linie de cod din logica internă a accordion-ului.

Trade-off-uri reale: Când NU este potrivit acest pattern

Nimic nu e gratuit în arhitectura software, iar compound components vin cu provocările lor:

  1. Performanță la liste lungi: Dacă randezi un accordion cu peste 50 de iteme și schimbi starea din context fără un useMemo sau o librărie de stare fină, toate sub-componentele înregistrate în context se pot re-randa. La un proiect cu 8k useri pe o listă lungă de audit logs, am observat un lag de 120ms la expandare până am separat contextul de stare de cel de dispatch.
  2. Contract mai lax cu dezvoltatorii: Nu mai poți garanta la nivel de TypeScript că un <AccordionTrigger> se află neapărat în interiorul unui <AccordionItem>. Dacă un coleg pune componentele aiurea, eroarea apare la runtime (când custom hook-ul aruncă throw new Error), nu la build time.

Concluzie

Dacă construiești o librărie internă de componente de design system, compound components este pattern-ul implicit pe care ar trebui să-l folosești pentru widget-uri complexe. Câștigi o flexibilitate masivă și scapi de prop-drilling, cu prețul unei atenții sporite la optimizarea re-renderărilor.

Voi mai folosiți monoliți cu array-uri de props pentru UI complicat sau ați trecut complet pe primitives decuplate?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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