State Management
This document explains the state management approach used in the Rhesis frontend application.
State Management Architecture
The frontend uses:
- React Context: For global/ambient state shared across components
- Local Component State: For component-specific state
- Server-side fetching: For entity-detail pages, via a server-only API client factory
- Client-side fetching: For interactive list/filter views, via
useState/useEffectand the BFF-proxied API client @tanstack/react-query: For mutations in some feature hooks
There is no Redux/Zustand store and no "use server" Server Actions in this codebase — mutations go through client-side calls to typed API client classes.
React Context
Global state lives in src/contexts/: ActiveProjectContext, FeaturesContext, NavigationItemsContext, OnboardingContext, OrganizationContext, OrgSettingsContext, PermissionsContext, WebSocketContext.
Theme Context
src/components/providers/ThemeProvider.tsx wraps MUI’s own ThemeProvider and exposes a ColorModeContext (not a generic useTheme() hook):
Consumers read it directly:
The cookie (not just localStorage) lets the server read the theme mode too. Mode is binary ('light' | 'dark') — there’s no separate 'system' value, though the initial mode falls back to the OS preference via prefers-color-scheme when nothing is stored yet.
Server-Side Fetching (Detail Pages)
Entity-detail pages fetch server-side using createServerApiFactory(), which attaches the session token — this only runs in Server Components, Route Handlers, and other server-only code:
Client-Side Fetching (List/Filter Views)
List pages are typically thin Server Components that render a client wrapper, which does its own fetching so pagination/filtering don’t require a full page reload:
Never pass a session token from client code. ApiClientFactory must be instantiated with no
arguments in client components/hooks — buildAuthHeaders() only attaches Authorization when
running server-side, so a client-side token is silently dropped. Gate on auth state with
isAuthenticated(status) / useIsAuthenticated(), not on token presence. See the BFF Auth
Pattern in apps/frontend/AGENTS.md.
Pagination for grid views is generally driven by local component state feeding MUI X Data Grid, not URL search params — though useSearchParams/usePathname are used elsewhere for shareable filter state.
Local Component State
For component-specific state, use React’s built-in hooks (useState, useReducer) — no additional library is needed.
Mutations with React Query
Some feature hooks wrap mutations with @tanstack/react-query:
Note this hook exposes mutation functions, not a cached task list — callers that need a list fetch it separately.
State Management Best Practices
- Minimize client-side state: prefer server-side fetching for detail/read-only pages
- Never prop-drill a session token into client components — always go through the BFF (
ApiClientFactory()with no args) - Context for global state: use React Context for theme, active project, permissions, and similar cross-cutting concerns
- Colocate local state: keep component state as close as possible to where it’s used
- Gate on auth status, not token presence —
isAuthenticated(status)/useIsAuthenticated()