import { create } from 'zustand';
interface FilterState {
filterText: string;
chartData: any[];
setFilterText: (text: string) => void;
}
// Store global simplu, fără boilerplate de Provider
export const useFilterStore = create<FilterState>((set) => ({
filterText: '',
chartData: [],
setFilterText: (text) => set({ filterText: text }),
}));
// În componentă: se re-randează DOAR când se schimbă filterText
const SearchInput = () => {
const filterText = useFilterStore((state) => state.filterText);
const setFilterText = useFilterStore((state) => state.setFilterText);
return (
<input
value={filterText}
onChange={(e) => setFilterText(e.target.value)}
/>
);
};Salutare tuturor. Am văzut tot mai des discuția asta pe forumuri și m-am lovit și eu de ea acum câteva luni la un proiect destul de mare, un dashboard financiar cu vreo 12.000 de utilizatori activi zilnic. Trebuia să gestionăm un flux destul de stufos de filtrare și configurare de rapoarte. Am pornit inițial pe varianta clasică: useReducer combinat cu React Context. Mare greșeală.
Când te fură peisajul cu useReducer + Context
La început, ideea sună super bine. E nativ, nu adaugi nicio dependență în package.json și ai structura aia curată de tip Redux: acțiuni, reducer pur, totul predictibil. Am scris reducerul, am trântit un Context Provider peste toată pagina de rapoarte și am zis că am rezolvat problema.
Problema a apărut când formularul de filtrare a început să crească. Aveam inputuri text, selectoare multiple și toggle-uri. La fiecare apăsare de tastă, dispatch-ul trimitea acțiunea, reducerul actualiza starea globală a paginii, iar Contextul re-randa absolut tot arborele de componente. Chiar și componentele secundare, care aveau nevoie doar de o listă statică de opțiuni, se redesenau pe ecran. Profilerul din React DevTools era complet roșu.
Trade-off-ul major la useReducer + Context este că nu ai selecție fină (fine-grained reactivity). Când contextul se schimbă, tot ce consumă useContext se re-randeză, indiferent dacă s-a schimbat sau nu cheia din state care interesează acea componentă. Am încercat să optimizez cu useMemo și să sparg contextul în trei bucăți mai mici, dar codul a devenit rapid un coșmar greu de citit și de menținut.
De ce Zustand e "cheat code" în React modern
Sătul de optimizări manuale care oricum nu dădeau rezultate fantastice, am decis să testez Zustand. Trecerea a durat cam o după-amiază și am scăpat instant de 30% din codul de boilerplate. Am eliminat Providerii și fișierele separate de tipuri pentru acțiuni.
Zustand folosește un model bazat pe selecție directă. Componentele se abonează doar la feliile de stare de care au cu adevărat nevoie. Dacă schimbi state.filterText, componenta care randează graficul (și ascultă doar de state.chartData) nici măcar nu clipește. Am obținut o reducere masivă a timpului de randare pe interacțiune, scăzând de la 45ms la sub 5ms pe mașinile mai slabe ale clienților.
Totuși, când rămânem la useReducer?
Zustand e excelent pentru stări globale sau semi-globale. Dar are o limitare de design: fiind un store de tip singleton în afara arborelui React, devine destul de complicat când ai nevoie de instanțe multiple ale aceleiași componente pe pagină, fiecare cu starea ei complet izolată.
Dacă ai un widget de chat complex sau un editor pe care vrei să-l poți deschide în trei instanțe diferite pe același ecran, starea lor trebuie să fie locală. Aici useReducer (fără Context global, doar local în interiorul componentei părinte) este sfânt. Îți oferă acea logică robustă de mașină de stări fără riscul ca widgetul A să își scrie din greșeală datele peste widgetul B.
Cum ați rezolvat problemele astea la proiectele voastre? Ați rămas fani Context pentru stări globale sau ați trecut complet pe Zustand / Jotai?