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:
- Performanță la liste lungi: Dacă randezi un accordion cu peste 50 de iteme și schimbi starea din context fără un
useMemosau 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. - 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?