Skip to main content

State management

redux-toolkit/ · hooks/redux/ · hooks/react-query/ · hooks/utils/ · services/

:::info Rule of thumb Redux = small, persisted, synchronous client state. React Query = all server-derived data and caching. Context = ergonomic derivation of that data (session authz, date/time). Plain hooks + the event emitter = ephemeral or cross-cutting concerns that don't belong in any store. :::

Redux Toolkit (redux-toolkit/)​

SliceStateNotes
globalSlicecounter, openSidebar, sidebarCollapsedPure UI/layout state — sidebar open/collapsed toggles.
userSliceisLoggedIn, userDataLegacy login snapshot; largely superseded by SessionContextProvider.
permissionsSlicepermissions: string[]Bootstrap/fallback permission cache, seeded from SSR cookies then continuously overwritten by live session-context data. Explicitly deprecated for authorization decisions.

Persistence: store.ts wraps the combined reducer with redux-persist, using a custom encrypted storage engine (encryptedStorage.ts) — AES via crypto-js, keyed by NEXT_APP_CR_SECRET_KEY — instead of plain localStorage, so the entire persisted state tree is opaque at rest.

hooks/redux/useAppDispatch.tsx / useAppSelector.tsx are the canonical typed wrappers around react-redux's hooks — used app-wide instead of the untyped originals.

React Query configuration​

Configured inline in pages/_app.tsx (no separate client file):

new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
retry: 0,
staleTime: 30_000, // 30s
gcTime: 300_000, // 5 min
},
},
});

Tuned for a backend in eu-north-1 (~188ms RTT from the US) — the comment in source explains call sites needing live data should explicitly opt out with staleTime: 0 (e.g. risk/money-sensitive queries).

Utility hooks & event bus​

HookPurpose
useDebounceDebounced copy of a value (search inputs).
useToggle{state, toggle, handleopen, handleclose} for modals/drawers.
useOnScreenIntersectionObserver wrapper for viewport visibility.
useDetectOnlineOnline/offline toast on connectivity change.
useEventEmitter + useLatestRefSubscribes a callback to the shared eventemitter3 bus without stale closures.
useCustomMediaQueryBreakpoints beyond MUI defaults — large/2K/4K/8K screen support for the dashboard.
useResizeHookvw-based pixel sizing for chart/canvas layout.

services/event.emitter.ts is a single shared eventemitter3 instance, mounted via components/EventListener/EventListener.tsx in _app.tsx, used for cross-cutting notifications (e.g. toast-on-error from the axios interceptor) that need to reach arbitrary components without prop drilling.