# State Management Schema - Maternal Organization App ## Store Architecture Overview ### Redux Toolkit Structure ```typescript // Core principles: // - Single source of truth // - Normalized state shape // - Offline-first design // - Optimistic updates // - Automatic sync queue ``` --- ## Root Store Structure ```typescript interface RootState { auth: AuthState; user: UserState; family: FamilyState; children: ChildrenState; activities: ActivitiesState; ai: AIState; sync: SyncState; offline: OfflineState; ui: UIState; notifications: NotificationState; analytics: AnalyticsState; } ``` --- ## Auth Slice ### State Shape ```typescript interface AuthState { isAuthenticated: boolean; accessToken: string | null; refreshToken: string | null; tokenExpiry: number | null; deviceFingerprint: string; trustedDevices: string[]; authStatus: 'idle' | 'loading' | 'succeeded' | 'failed'; error: string | null; } ``` ### Actions ```typescript // authSlice.ts const authSlice = createSlice({ name: 'auth', initialState, reducers: { loginStart: (state) => { state.authStatus = 'loading'; }, loginSuccess: (state, action) => { state.isAuthenticated = true; state.accessToken = action.payload.accessToken; state.refreshToken = action.payload.refreshToken; state.tokenExpiry = action.payload.expiresAt; state.authStatus = 'succeeded'; }, loginFailure: (state, action) => { state.authStatus = 'failed'; state.error = action.payload; }, tokenRefreshed: (state, action) => { state.accessToken = action.payload.accessToken; state.tokenExpiry = action.payload.expiresAt; }, logout: (state) => { return initialState; }, }, }); ``` --- ## User Slice ### State Shape ```typescript interface UserState { currentUser: { id: string; email: string; name: string; locale: string; timezone: string; photoUrl?: string; preferences: UserPreferences; } | null; subscription: { tier: 'free' | 'premium' | 'plus'; expiresAt?: string; aiQueriesUsed: number; aiQueriesLimit: number; }; } interface UserPreferences { darkMode: 'auto' | 'light' | 'dark'; notifications: { push: boolean; email: boolean; quietHoursStart?: string; quietHoursEnd?: string; }; measurementUnit: 'metric' | 'imperial'; } ``` --- ## Family Slice ### State Shape ```typescript interface FamilyState { currentFamily: { id: string; name: string; shareCode: string; createdBy: string; } | null; members: { byId: Record; allIds: string[]; }; invitations: Invitation[]; loadingStatus: LoadingStatus; } interface FamilyMember { id: string; name: string; email: string; role: 'parent' | 'caregiver' | 'viewer'; permissions: Permissions; lastActive: string; isOnline: boolean; } ``` ### Normalized Actions ```typescript const familySlice = createSlice({ name: 'family', initialState, reducers: { memberAdded: (state, action) => { const member = action.payload; state.members.byId[member.id] = member; state.members.allIds.push(member.id); }, memberUpdated: (state, action) => { const { id, changes } = action.payload; state.members.byId[id] = { ...state.members.byId[id], ...changes, }; }, memberRemoved: (state, action) => { const id = action.payload; delete state.members.byId[id]; state.members.allIds = state.members.allIds.filter(mid => mid !== id); }, }, }); ``` --- ## Children Slice ### State Shape ```typescript interface ChildrenState { children: { byId: Record; allIds: string[]; }; activeChildId: string | null; milestones: { byChildId: Record; }; } interface Child { id: string; name: string; birthDate: string; gender?: string; photoUrl?: string; medical: { bloodType?: string; allergies: string[]; conditions: string[]; medications: Medication[]; }; metrics: { currentWeight?: Measurement; currentHeight?: Measurement; headCircumference?: Measurement; }; } ``` --- ## Activities Slice (Normalized) ### State Shape ```typescript interface ActivitiesState { activities: { byId: Record; allIds: string[]; byChild: Record; // childId -> activityIds byDate: Record; // date -> activityIds }; activeTimers: { [childId: string]: ActiveTimer; }; filters: { childId?: string; dateRange?: { start: string; end: string }; types?: ActivityType[]; }; pagination: { cursor: string | null; hasMore: boolean; isLoading: boolean; }; } interface Activity { id: string; childId: string; type: ActivityType; timestamp: string; duration?: number; details: ActivityDetails; loggedBy: string; syncStatus: 'synced' | 'pending' | 'error'; version: number; // For conflict resolution } interface ActiveTimer { activityType: ActivityType; startTime: number; pausedDuration: number; isPaused: boolean; } ``` ### Activity Actions ```typescript const activitiesSlice = createSlice({ name: 'activities', initialState, reducers: { // Optimistic update activityLogged: (state, action) => { const activity = { ...action.payload, syncStatus: 'pending', }; state.activities.byId[activity.id] = activity; state.activities.allIds.unshift(activity.id); // Update indexes if (!state.activities.byChild[activity.childId]) { state.activities.byChild[activity.childId] = []; } state.activities.byChild[activity.childId].unshift(activity.id); }, // Sync confirmed activitySynced: (state, action) => { const { localId, serverId } = action.payload; state.activities.byId[localId].id = serverId; state.activities.byId[localId].syncStatus = 'synced'; }, // Timer management timerStarted: (state, action) => { const { childId, activityType } = action.payload; state.activeTimers[childId] = { activityType, startTime: Date.now(), pausedDuration: 0, isPaused: false, }; }, }, }); ``` --- ## AI Slice ### State Shape ```typescript interface AIState { conversations: { byId: Record; activeId: string | null; }; insights: { byChildId: Record; pending: Insight[]; }; predictions: { byChildId: Record; }; quotas: { dailyQueries: number; dailyLimit: number; resetAt: string; }; } interface Conversation { id: string; childId?: string; messages: Message[]; context: ConversationContext; lastMessageAt: string; } interface Predictions { nextNapTime?: { time: string; confidence: number }; nextFeedingTime?: { time: string; confidence: number }; growthSpurt?: { likelihood: number; expectedIn: string }; } ``` --- ## Sync Slice (Critical for Offline) ### State Shape ```typescript interface SyncState { queue: SyncQueueItem[]; conflicts: ConflictItem[]; lastSync: { [entityType: string]: string; // ISO timestamp }; syncStatus: 'idle' | 'syncing' | 'error' | 'offline'; retryCount: number; webSocket: { connected: boolean; reconnectAttempts: number; }; } interface SyncQueueItem { id: string; type: 'CREATE' | 'UPDATE' | 'DELETE'; entity: 'activity' | 'child' | 'family'; payload: any; timestamp: string; retries: number; error?: string; } interface ConflictItem { id: string; localVersion: any; serverVersion: any; strategy: 'manual' | 'local' | 'server' | 'merge'; } ``` ### Sync Actions ```typescript const syncSlice = createSlice({ name: 'sync', initialState, reducers: { addToQueue: (state, action) => { state.queue.push({ id: nanoid(), ...action.payload, timestamp: new Date().toISOString(), retries: 0, }); }, removeFromQueue: (state, action) => { state.queue = state.queue.filter(item => item.id !== action.payload); }, conflictDetected: (state, action) => { state.conflicts.push(action.payload); }, conflictResolved: (state, action) => { const { id, resolution } = action.payload; state.conflicts = state.conflicts.filter(c => c.id !== id); // Apply resolution... }, syncCompleted: (state, action) => { state.lastSync[action.payload.entity] = new Date().toISOString(); state.syncStatus = 'idle'; }, }, }); ``` --- ## Offline Slice ### State Shape ```typescript interface OfflineState { isOnline: boolean; queuedActions: OfflineAction[]; cachedData: { [key: string]: { data: any; timestamp: string; ttl: number; }; }; retryPolicy: { maxRetries: number; retryDelay: number; backoffMultiplier: number; }; } interface OfflineAction { id: string; action: AnyAction; meta: { offline: { effect: any; commit: AnyAction; rollback: AnyAction; }; }; } ``` --- ## UI Slice ### State Shape ```typescript interface UIState { theme: 'light' | 'dark' | 'auto'; activeScreen: string; modals: { [modalId: string]: { isOpen: boolean; data?: any; }; }; loading: { [key: string]: boolean; }; errors: { [key: string]: ErrorInfo; }; toasts: Toast[]; bottomSheet: { isOpen: boolean; content: 'quickActions' | 'activityDetails' | null; }; } interface Toast { id: string; type: 'success' | 'error' | 'info'; message: string; duration: number; } ``` --- ## Middleware Configuration ### Store Setup ```typescript // store/index.ts import { configureStore } from '@reduxjs/toolkit'; import { persistStore, persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER, } from 'redux-persist'; import AsyncStorage from '@react-native-async-storage/async-storage'; const persistConfig = { key: 'root', storage: AsyncStorage, whitelist: ['auth', 'user', 'children', 'activities'], blacklist: ['ui', 'sync'], // Don't persist UI state }; const rootReducer = combineReducers({ auth: authReducer, user: userReducer, family: familyReducer, children: childrenReducer, activities: activitiesReducer, ai: aiReducer, sync: syncReducer, offline: offlineReducer, ui: uiReducer, notifications: notificationsReducer, analytics: analyticsReducer, }); const persistedReducer = persistReducer(persistConfig, rootReducer); export const store = configureStore({ reducer: persistedReducer, middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: { ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], }, }).concat([ syncMiddleware, offlineMiddleware, analyticsMiddleware, conflictResolutionMiddleware, ]), }); export const persistor = persistStore(store); ``` ### Sync Middleware ```typescript // middleware/syncMiddleware.ts export const syncMiddleware: Middleware = (store) => (next) => (action) => { const result = next(action); // Queue actions for sync if (action.type.includes('activities/') && !action.meta?.skipSync) { const state = store.getState(); if (!state.offline.isOnline) { store.dispatch(addToQueue({ type: 'UPDATE', entity: 'activity', payload: action.payload, })); } else { // Sync immediately syncActivity(action.payload); } } return result; }; ``` ### Offline Middleware ```typescript // middleware/offlineMiddleware.ts export const offlineMiddleware: Middleware = (store) => (next) => (action) => { // Check network status if (action.type === 'network/statusChanged') { const isOnline = action.payload; if (isOnline && store.getState().sync.queue.length > 0) { // Process offline queue store.dispatch(processOfflineQueue()); } } // Handle optimistic updates if (action.meta?.offline) { const { effect, commit, rollback } = action.meta.offline; // Apply optimistic update next(action); // Attempt sync effect() .then(() => next(commit)) .catch(() => next(rollback)); return; } return next(action); }; ``` --- ## Selectors ### Memoized Selectors ```typescript // selectors/activities.ts import { createSelector } from '@reduxjs/toolkit'; export const selectActivitiesByChild = createSelector( [(state: RootState) => state.activities.activities.byId, (state: RootState, childId: string) => state.activities.activities.byChild[childId]], (byId, activityIds = []) => activityIds.map(id => byId[id]).filter(Boolean) ); export const selectTodaysSummary = createSelector( [(state: RootState, childId: string) => selectActivitiesByChild(state, childId)], (activities) => { const today = new Date().toDateString(); const todaysActivities = activities.filter( a => new Date(a.timestamp).toDateString() === today ); return { feedings: todaysActivities.filter(a => a.type === 'feeding').length, sleepHours: calculateSleepHours(todaysActivities), diapers: todaysActivities.filter(a => a.type === 'diaper').length, }; } ); ``` --- ## Conflict Resolution ### Conflict Resolution Strategy ```typescript // utils/conflictResolution.ts export const resolveConflict = ( local: Activity, remote: Activity ): Activity => { // Last write wins for simple conflicts if (local.version === remote.version) { return local.timestamp > remote.timestamp ? local : remote; } // Server version is higher - merge changes if (remote.version > local.version) { return { ...remote, // Preserve local notes if different details: { ...remote.details, notes: local.details.notes !== remote.details.notes ? `${remote.details.notes}\n---\n${local.details.notes}` : remote.details.notes, }, }; } // Local version is higher return local; }; ``` --- ## Performance Optimizations ### Normalized State Updates ```typescript // Batch updates for performance const activitiesSlice = createSlice({ name: 'activities', reducers: { activitiesBatchUpdated: (state, action) => { const activities = action.payload; // Use immer's batching activities.forEach(activity => { state.activities.byId[activity.id] = activity; if (!state.activities.allIds.includes(activity.id)) { state.activities.allIds.push(activity.id); } }); }, }, }); ``` ### Lazy Loading ```typescript // Lazy load historical data export const loadMoreActivities = createAsyncThunk( 'activities/loadMore', async ({ cursor, limit = 20 }, { getState }) => { const response = await api.getActivities({ cursor, limit }); return response.data; }, { condition: (_, { getState }) => { const state = getState() as RootState; return !state.activities.pagination.isLoading; }, } ); ```