State Management
Division
| Concern | Tool |
|---|---|
| Server/async data | TanStack React Query |
| Cart, config, auth profile | Zustand + persist |
| URL filters | useSearchParams |
| Form state | React Hook Form |
| Feature-scoped (checkout draft) | Zustand in features/checkout/store/ |
Zustand Stores
| Store | Persist key | Purpose |
|---|---|---|
| cart | {brand}-cart |
Line items |
| config | config-storage |
Country, currency, exchange rates |
| profile | — | Auth user (no persist) |
| favorite | persisted | Wishlist |
| coupon | persisted | Applied promo |
| cart-ui | — | Sheet open state |
Store Pattern
export const useConfigStore = create<ConfigStore>()(
persist((set, get) => ({
selectedCountryId: 456,
exchangeRates: {},
setSelectedCountry: (id) => set({ selectedCountryId: id }),
}), {
name: 'config-storage',
partialize: (s) => ({ selectedCountryId: s.selectedCountryId }), // exclude isLoading
})
);
Cross-store: useConfigStore.getState() inside cart store.
React Query Defaults
staleTime: 2 * 60 * 1000,
gcTime: 5 * 60 * 1000,
refetchOnWindowFocus: false,
retry: 2,
Query Key Factory
export const productsKeys = {
all: ['products'] as const,
lists: () => [...productsKeys.all, 'list'] as const,
list: (params) => [...productsKeys.lists(), params] as const,
detail: (slug) => [...productsKeys.all, 'detail', slug] as const,
};
Colocate with service file.
Invalidation
queryClient.invalidateQueries({ queryKey: productsKeys.all });
Auth State Flow
ProfileService ↔ useProfileStore ↔ AuthProvider ↔ useAuth()
Token in cookie — store holds user object only.