import { create } from 'zustand';
interface CartStore {
items: string[];
addItem: (item: string) => void;
reset: () => void;
}
export const useCartStore = create<CartStore>((set) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
reset: () => set({ items: [] }),
}));
// Consumator optimizat — randare doar când se schimbă lungimea:
export const CartBadge = () => {
const count = useCartStore((state) => state.items.length);
return <span>{count}</span>;
};Am văzut săptămâna trecută un PR unde un coleg junior a trântit un store Zustand complet ca să gestioneze un formular de înregistrare în trei pași. Codul mergea, dar am lăsat comentariu să facem un pas în spate și să discutăm de ce ne complicăm aiurea.
Nu e prima dată când pățesc asta. Avem tendința să sărim direct la librării externe sau, la extrema cealaltă, să ne chinuim cu unelte native până ne iese peri albi.
Cazul useReducer: stări interconectate pe o singură pagină
useReducer strălucește când ai o bucată de interfață cu logică densă, dar complet izolată de restul aplicației. Gândește-te la un data-table complex: ai sortare pe coloane, paginare, filtre multiple și selecție de rânduri.
La un proiect intern cu vreo 15 filtre dependente unele de altele, aveam inițial vreo 8 bucăți de useState. Când userul reseta filtrele, declanșam o cascadă de re-randări de mureai lângă monitor. Trecerea la un singur useReducer a rezolvat problema instant. Toate tranzițiile au devenit atomice, iar funcția de reducer am testat-o pur unitar, în Node, fără să instanțiez măcar un DOM fals.
Trade-off-ul e evident: de îndată ce vrei starea aia la trei componente distanță, începe durerea. Fie faci prop drilling până uiți ce scrii, fie împachetezi totul într-un React.createContext. Și aici e capcana: Context API nu a fost gândit ca store de înaltă frecvență. Când dispatch-ul schimbă starea din context, fiecare componentă care citește contextul ăla se re-randează, dacă nu stai să spargi providerul în bucăți mici sau să folosești memoizări agresive.
Cazul Zustand: simplitate globală fără provider hell
Zustand e poate cea mai plăcută chestie apărută în ecosistemul de state management de la React Query încoace. Îl folosesc cu drag când starea chiar trebuie să trăiască independent de ciclul de viață al unei singure pagini: datele de sesiune, drawer-ul de notificări sau coșul de cumpărături.
Ce câștig direct? În primul rând, n-ai nevoie de niciun Provider la rădăcina arborelui. În al doilea rând, selectoarele native previn re-randările inutile fără efort mental suplimentar. Poți citi starea chiar și în afara componentelor React, cum ar fi într-un interceptor de Axios, lucru care cu useReducer e imposibil fără artificii urâte.
Dar există și un dezavantaj serios, pe care îl văd ignorat prea des. Pentru că e atât de simplu să scrii un store, devine un magnet pentru stări care ar fi trebuit să moară odată cu demontarea ecranului. La un SaaS cu vreo 12k utilizatori activi am găsit stări de căutare rămase în memorie după ce omul schimbase ruta de trei ori. Dacă nu-ți cureți manual store-ul la unmount, rămâi cu date fantomă și bug-uri aproape imposibil de reprodus.
Regula practică
Dacă starea trebuie să moară când userul părăsește ecranul, locul ei e într-un hook local (useState sau useReducer). Ține ciclul de viață legat de arborele de componente.
Dacă ai nevoie de date în mai multe ramuri decuplate sau vrei să citești ceva din afara arborelui React, fă un store de Zustand fără să te uiți înapoi.
Voi cum abordați asta? Mai folosește cineva useReducer combinat cu Context în 2024 sau a devenit Zustand noul default pentru orice depășește un simplu boolean?