| Field | Value |
|---|---|
| Status | Accepted |
| Date | 2026-05-21 |
| Context | UMS Web App — State Management Strategy |
| Deciders | Architecture Team |
React applications need to manage two fundamentally different types of state:
Using a single solution for both leads to over-engineering (server state in Redux) or under-engineering (client state with manual fetch logic).
Use a dual-strategy approach:
// Queries are cached, deduplicated, and auto-invalidated
const { data, isLoading } = useQuery({
queryKey: ['tenants', page, filters],
queryFn: () => tenantService.getTenants(page, filters),
staleTime: 30_000,
});
// Mutations invalidate queries and show notifications
const createMutation = useNotifiedMutation({
mutationFn: (data) => tenantService.createTenant(data),
invalidateKeys: [['tenants']],
successNotif: () => ({ title: 'Created', message: 'Tenant created' }),
errorNotif: (err) => ({ title: 'Error', message: getHttpErrorMessage(err) }),
});
// Simple, fast, TypeScript-first state management
export const useThemeStore = create<ThemeState>()(
persist(
(set) => ({
isDarkMode: true,
toggleDarkMode: () => set((s) => ({ isDarkMode: !s.isDarkMode })),
}),
{ name: 'ums-theme' },
),
);
| Store | Purpose | Persistence |
|---|---|---|
auth.store |
User session, authentication state | No (session-only) |
theme.store |
Dark/light mode preference | Yes (localStorage) |
notification.store |
In-app notifications (cap: 50) | No (session-only) |
i18n.store |
Active language (en/es) |
No (syncs with document.lang) |
devTools.store |
Dev-only overrides (user impersonation) | No (dev-only) |
devTools.store is development-only; production code uses i18n.store and auth.store.All mutations follow the same pattern via useNotifiedMutation:
Positive:
persist middleware for localStorage without custom codeuseNotifiedMutation eliminates mutation boilerplateNegative:
persist)src/application/stores/ — All Zustand storessrc/application/hooks/use-notified-mutation.ts — Mutation factorysrc/infrastructure/http/ — HTTP and GraphQL clientsvitest.config.ts — Coverage thresholds for state code