Files
maternal-app/maternal-web/store/hooks.ts
Andrei 7cb2ff97de
Some checks failed
CI/CD Pipeline / Lint and Test (push) Has been cancelled
CI/CD Pipeline / E2E Tests (push) Has been cancelled
CI/CD Pipeline / Build Application (push) Has been cancelled
feat: Implement offline-first Redux architecture with optimistic updates
Implemented comprehensive offline-first state management:

Redux Store Setup:
- Configure Redux Toolkit with @redux-offline/redux-offline
- Setup redux-persist with IndexedDB (localforage)
- Custom offline config with exponential backoff retry
- Normalized state with entity adapters

State Slices:
- activitiesSlice: Normalized activities with optimistic CRUD
- childrenSlice: Normalized children with optimistic CRUD
- networkSlice: Network status and connection quality
- offlineSlice: Sync queue and pending actions

Middleware:
- offlineMiddleware: Queue actions when offline
- syncMiddleware: Process pending actions when online
- Conflict resolution strategies (SERVER_WINS, CLIENT_WINS, LAST_WRITE_WINS, MERGE)
- Version-based conflict detection

Features:
- Optimistic updates for immediate UI feedback
- Automatic sync queue with retry logic (5 retries max)
- Network detection (browser events + periodic checks)
- Connection quality monitoring (excellent/good/poor/offline)
- Latency tracking
- Conflict resolution with multiple strategies
- Entity versioning for optimistic updates

Components:
- NetworkStatusIndicator: Full-screen status banner
- NetworkStatusBadge: Compact app bar badge
- ReduxProvider: Provider with network detection setup

Custom Hooks:
- useAppDispatch/useAppSelector: Typed Redux hooks
- useIsOnline: Check online status
- useHasPendingSync: Check for pending actions
- useSyncStatus: Get sync progress info
- useOptimisticAction: Combine optimistic + actual actions
- useNetworkQuality: Get connection quality
- useIsOptimistic: Check if entity is being synced

Documentation:
- Comprehensive README with usage examples
- Architecture overview
- Best practices guide
- Troubleshooting section

State Structure:
- Normalized entities with byId/allIds
- Optimistic metadata (_optimistic, _localId, _version)
- Entity adapters with memoized selectors
- Offline queue persistence to IndexedDB

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-01 19:24:46 +00:00

94 lines
2.7 KiB
TypeScript

import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';
import type { RootState, AppDispatch } from './store';
import { useCallback } from 'react';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
// Custom hooks for common patterns
/**
* Hook to check if the app is online
*/
export const useIsOnline = () => {
return useAppSelector((state) => state.network?.isOnline ?? true);
};
/**
* Hook to check if there are pending sync actions
*/
export const useHasPendingSync = () => {
const pendingActions = useAppSelector((state) => state.offline?.pendingActions ?? []);
return pendingActions.length > 0;
};
/**
* Hook to get sync status
*/
export const useSyncStatus = () => {
const syncInProgress = useAppSelector((state) => state.offline?.syncInProgress ?? false);
const lastSyncTime = useAppSelector((state) => state.offline?.lastSyncTime);
const pendingCount = useAppSelector((state) => state.offline?.pendingActions?.length ?? 0);
return {
syncing: syncInProgress,
lastSync: lastSyncTime,
pendingCount,
};
};
/**
* Hook for optimistic updates
* Provides a function that dispatches both optimistic and actual actions
*/
export const useOptimisticAction = <T extends any[], R>(
optimisticAction: (...args: T) => any,
actualAction: (...args: T) => any
) => {
const dispatch = useAppDispatch();
const isOnline = useIsOnline();
return useCallback(
async (...args: T) => {
// Always dispatch optimistic action for immediate UI update
dispatch(optimisticAction(...args));
// If online, dispatch actual action
if (isOnline) {
try {
const result = await dispatch(actualAction(...args));
return result;
} catch (error) {
// Rollback will be handled by the rejected case in the slice
throw error;
}
}
// If offline, the action will be queued by the middleware
return Promise.resolve();
},
[dispatch, optimisticAction, actualAction, isOnline]
);
};
/**
* Hook to get network quality information
*/
export const useNetworkQuality = () => {
const quality = useAppSelector((state) => state.network?.connectionQuality ?? 'excellent');
const latency = useAppSelector((state) => state.network?.latency);
return { quality, latency };
};
/**
* Hook to check if a specific entity is being optimistically updated
*/
export const useIsOptimistic = (entityType: 'activities' | 'children', id: string) => {
return useAppSelector((state) => {
const entity = state[entityType]?.entities?.[id];
return entity?._optimistic ?? false;
});
};