# State Management The application uses Redux Toolkit with Redux-Persist for robust state management. ## Store Configuration **File:** `store/index.ts` ```typescript // Combines all slices with persistence const persistConfig = { key: 'root', storage, whitelist: ['auth', 'telegram'], // Persisted slices }; ``` ## Redux State Structure ```typescript RootState = { auth: { token: string | null, // JWT (persisted) isOnboardedByUser: Record, // Per-user flag (persisted) }, socket: { byUser: Record< string, { // Per user ID status: 'connecting' | 'connected' | 'disconnected'; socketId: string | null; } >, }, user: { profile: User | null, loading: boolean, error: string | null }, telegram: { byUser: Record, // Per Telegram user (persisted) }, }; ``` ## Slices ### Auth Slice (`store/authSlice.ts`) Manages JWT token and per-user onboarding status. **State:** ```typescript interface AuthState { token: string | null; isOnboardedByUser: Record; } ``` **Actions:** - `setToken(token: string)` - Store JWT after login - `clearToken()` - Remove token on logout - `setOnboarded({ userId, isOnboarded })` - Mark user as onboarded **Selectors (`store/authSelectors.ts`):** - `selectToken` - Get current JWT - `selectIsOnboarded(userId)` - Check if user completed onboarding ### Socket Slice (`store/socketSlice.ts`) Tracks Socket.io connection status per user. **State:** ```typescript interface SocketState { byUser: Record< string, { status: 'connecting' | 'connected' | 'disconnected'; socketId: string | null } >; } ``` **Actions:** - `setSocketStatus({ userId, status })` - Update connection status - `setSocketId({ userId, socketId })` - Store socket ID - `clearSocketState(userId)` - Clear user's socket state **Selectors (`store/socketSelectors.ts`):** - `selectSocketStatus(userId)` - Get connection status - `selectIsSocketConnected(userId)` - Boolean connected check ### User Slice (`store/userSlice.ts`) Stores user profile data. **State:** ```typescript interface UserState { profile: User | null; loading: boolean; error: string | null; } ``` **Actions:** - `setUser(user)` - Store user profile - `setUserLoading(loading)` - Set loading state - `setUserError(error)` - Set error state - `clearUser()` - Clear profile on logout ### Telegram Slice (`store/telegram/`) Complex nested state management for Telegram integration. **Files:** - `index.ts` - Slice exports (actions, thunks) - `types.ts` - Entity and state interfaces - `reducers.ts` - Synchronous reducers - `extraReducers.ts` - Async thunk handlers - `thunks.ts` - Async operations **State Structure:** ```typescript telegram.byUser[telegramUserId] = { connectionStatus: "disconnected" | "connecting" | "connected" | "error", authStatus: "not_authenticated" | "authenticating" | "authenticated" | "error", currentUser: TelegramUser | null, sessionString: string | null, // Stored here, NOT localStorage chats: Record, chatsOrder: string[], messages: Record>, threads: Record } ``` **Reducers:** - `setCurrentUser` - Store authenticated Telegram user - `setSessionString` - Store MTProto session (for persistence) - `setConnectionStatus` - Update connection state - `setAuthStatus` - Update authentication state - `addChat` / `updateChat` - Manage chat list - `addMessage` / `updateMessage` - Manage message history - `setThreads` - Store thread data **Thunks (`store/telegram/thunks.ts`):** - `initializeTelegram(userId)` - Initialize MTProto client - `connectTelegram(userId)` - Establish Telegram connection - `fetchChats(userId)` - Load chat list - `fetchMessages({ userId, chatId })` - Load message history - `disconnectTelegram(userId)` - Clean disconnect **Selectors (`store/telegramSelectors.ts`):** - `selectTelegramState(userId)` - Get full Telegram state - `selectTelegramConnectionStatus(userId)` - Get connection status - `selectTelegramAuthStatus(userId)` - Get auth status - `selectTelegramChats(userId)` - Get chat list - `selectTelegramMessages(userId, chatId)` - Get messages for chat ## Typed Hooks **File:** `store/hooks.ts` ```typescript // Use these instead of plain useDispatch/useSelector export const useAppDispatch: () => AppDispatch = useDispatch; export const useAppSelector: TypedUseSelectorHook = useSelector; ``` ## Persistence Configuration ### What's Persisted - `auth.token` - JWT for authentication - `auth.isOnboardedByUser` - Per-user onboarding status - `telegram.byUser` - Telegram state (sessions, chats, etc.) ### What's NOT Persisted - `socket` - Connection state (reconnects on app start) - `user.loading` / `user.error` - Transient UI states - Telegram loading/error states ### Storage Backend Redux-Persist uses localStorage adapter by default. This is the ONLY acceptable use of localStorage in the application. ## Usage Examples ### Reading State ```typescript import { useAppSelector } from '../store/hooks'; function MyComponent() { const token = useAppSelector(state => state.auth.token); const isConnected = useAppSelector(state => state.socket.byUser[userId]?.status === 'connected'); const chats = useAppSelector(state => state.telegram.byUser[userId]?.chats); } ``` ### Dispatching Actions ```typescript import { clearToken, setToken } from '../store/authSlice'; import { useAppDispatch } from '../store/hooks'; import { initializeTelegram } from '../store/telegram/thunks'; function MyComponent() { const dispatch = useAppDispatch(); // Sync action const handleLogin = (token: string) => { dispatch(setToken(token)); }; // Async thunk const handleConnect = async () => { await dispatch(initializeTelegram(userId)).unwrap(); }; } ``` ### Using Selectors ```typescript import { selectIsOnboarded } from '../store/authSelectors'; import { useAppSelector } from '../store/hooks'; import { selectTelegramConnectionStatus } from '../store/telegramSelectors'; function MyComponent({ userId }) { const isOnboarded = useAppSelector(state => selectIsOnboarded(state, userId)); const connectionStatus = useAppSelector(state => selectTelegramConnectionStatus(state, userId)); } ``` ## Best Practices 1. **Always use typed hooks** - `useAppDispatch` and `useAppSelector` 2. **Use selectors for derived state** - Memoized and testable 3. **Keep thunks in separate files** - Better organization 4. **Per-user state scoping** - Key state by user ID 5. **Avoid localStorage** - Use Redux-Persist instead --- _Previous: [Architecture Overview](./01-architecture.md) | Next: [Services Layer](./03-services.md)_