This document covers the frontend state management architecture in the memos application, which uses React Query for server state management, React Context API for global client state, and Local Storage for persistence and cross-tab synchronization.
The memos application uses a hybrid state management approach. It separates concerns between server-side data (memos, users, settings) managed by React Query and application-level state (authentication, filtering, workspace configuration) managed by React Context providers.
State Management Architecture
Sources: web/src/contexts/AuthContext.tsx33-203 web/src/auth-state.ts9-46 web/src/hooks/useMemoQueries.ts12-27 web/src/hooks/useUserQueries.ts23-35 web/src/contexts/InstanceContext.tsx55-148
React Query manages all data fetched from the gRPC-web API. It handles caching, deduplication, and complex cache invalidation.
Consistent cache management is achieved through a centralized query key factory to ensure that invalidations target the correct cache entries.
| Factory | Keys | File |
|---|---|---|
memoKeys | all, lists, list(filters), details, detail(name), comments(name), linkMetadata(url) | web/src/hooks/useMemoQueries.ts19-27 |
userKeys | all, details, stats, userStats(name), allUserStats(request), currentUser, shortcuts, notifications | web/src/hooks/useUserQueries.ts23-35 |
The application uses optimistic updates for a snappy UI, particularly when updating memo content.
useUpdateMemo, the application snapshots the current cache and performs an optimistic update using patchMemoInCollectionQueries web/src/hooks/useMemoQueries.ts217-226useCreateMemo invalidates memoKeys.lists() and userKeys.stats() web/src/hooks/useMemoQueries.ts195-202Sources: web/src/hooks/useMemoQueries.ts187-241 web/src/hooks/useUserQueries.ts144-160
Context providers handle state that must be accessible across the entire component tree and don't strictly follow a request-response pattern.
Manages the current user session and related settings (general, webhooks, tags).
initialize function attempts to refresh the access token and then fetches the user profile via authServiceClient.getCurrentUser web/src/contexts/AuthContext.tsx78-137userKeys.currentUser() and userKeys.detail(name) web/src/contexts/AuthContext.tsx121-122isIdentityInitialized (user identity known) and isInitialized (settings also loaded) to allow route modules to start queries early web/src/contexts/AuthContext.tsx112-119Manages workspace-level configuration and the instance profile.
fetchSetting or fetchSettings to keep the initial load light web/src/contexts/InstanceContext.tsx150-189Sources: web/src/contexts/AuthContext.tsx14-58 web/src/contexts/InstanceContext.tsx28-63 web/src/hooks/index.ts1-15
Authentication state is handled partially outside the React lifecycle in web/src/auth-state.ts to allow access within the gRPC interceptor layer.
localStorage web/src/auth-state.ts6-8authInterceptor in web/src/connect.ts automatically attaches the Authorization: Bearer <token> header. It handles reactive refresh on 401 Unauthenticated errors web/src/connect.ts156-178tokenRefreshManager ensures that multiple concurrent 401 errors only trigger a single refresh request to the backend web/src/connect.ts30-51BroadcastChannel named memos_token_sync ensures that if one tab refreshes a token, all other open tabs adopt it immediately web/src/auth-state.ts28-46useTokenRefreshOnFocus hook refreshes the token when the tab becomes visible if it is expiring soon (within 2 minutes), preventing multiple 401 errors when React Query triggers window-focus refetches web/src/hooks/useTokenRefreshOnFocus.ts13-49Token Refresh Logic
Sources: web/src/auth-state.ts52-97 web/src/connect.ts89-91 web/src/connect.ts156-178 web/src/hooks/useTokenRefreshOnFocus.ts28-34
The application uses localStorage for UI preferences and persistent state:
useLocalStorage is a generic hook for persistent UI state web/src/hooks/useLocalStorage.tsSpecific invalidation patterns in useMutation hooks keep the UI synchronized:
| Action | Invalidation Target | Rationale |
|---|---|---|
| Create Memo | memoKeys.lists(), userKeys.stats() | Ensure new memo appears in lists and user statistics (memo count) are updated web/src/hooks/useMemoQueries.ts195-202 |
| Update Memo | memoKeys.detail(name) | Refresh the specific memo detail view web/src/hooks/useMemoQueries.ts238 |
| Delete Memo | memoKeys.lists(), userKeys.stats() | Remove from lists and update counts web/src/hooks/useMemoQueries.ts269-272 |
| Update User | userKeys.detail(name), userKeys.currentUser() | Sync profile changes across the UI web/src/hooks/useUserQueries.ts156-157 |
| Update Setting | userKeys.all (settings) | Refresh user-specific configurations web/src/hooks/useUserQueries.ts205 |
In scenarios where a component needs a snippet of a memo that might already be in the list cache, findMemoInCollectionQueries is used to avoid redundant network requests web/src/hooks/useMemoQueries.ts111-120 This is heavily utilized in useResolvedRelationMemos for hydrating memo relations web/src/components/MemoMetadata/Relation/useResolvedRelationMemos.ts25-29
Sources: web/src/hooks/useMemoQueries.ts187-276 web/src/hooks/useUserQueries.ts144-209 web/src/auth-state.ts118-128 web/src/components/MemoMetadata/Relation/useResolvedRelationMemos.ts22-46
Refresh this wiki
This wiki was recently refreshed. Please wait 6 days to refresh again.