# Socket & Memoization Patterns ## Quick Reference ### DO: - ✅ Track optimistic updates to prevent double-renders - ✅ Memoize socket event handlers with useCallback - ✅ Check if incoming data actually differs from current state - ✅ Use debouncing for rapid UI updates (drag & drop) - ✅ Clean up socket listeners in useEffect cleanup ### DON'T: - ❌ Update state without checking if data changed - ❌ Create new handler functions on every render - ❌ Apply server updates that match pending optimistic updates - ❌ Forget to handle the "modal open" edge case ## Pattern Examples ### Optimistic Update Pattern ```typescript import { useOptimisticUpdates } from '../../hooks/useOptimisticUpdates'; const MyComponent = () => { const { addPendingUpdate, isPendingUpdate } = useOptimisticUpdates(); const handleLocalUpdate = (task: Task) => { // Track the optimistic update addPendingUpdate({ id: task.id, timestamp: Date.now(), data: task, operation: 'update' }); // Update local state immediately setTasks(prev => prev.map(t => t.id === task.id ? task : t)); // Persist to server api.updateTask(task); }; const handleServerUpdate = useCallback((task: Task) => { // Skip if this is our own update echoing back if (isPendingUpdate(task.id, task)) { console.log('Skipping own optimistic update'); return; } // Apply server update setTasks(prev => prev.map(t => t.id === task.id ? task : t)); }, [isPendingUpdate]); }; ``` ### Socket Handler Pattern ```typescript import { useSocketSubscription } from '../../hooks/useSocketSubscription'; const MyComponent = () => { // Option 1: Using the hook useSocketSubscription( socketService, 'data_updated', (data) => { console.log('Data updated:', data); // Handle update }, [/* dependencies */] ); // Option 2: Manual memoization const handleUpdate = useCallback((message: any) => { const data = message.data || message; setItems(prev => { // Check if data actually changed const existing = prev.find(item => item.id === data.id); if (existing && JSON.stringify(existing) === JSON.stringify(data)) { return prev; // No change, prevent re-render } return prev.map(item => item.id === data.id ? data : item); }); }, []); useEffect(() => { socketService.addMessageHandler('update', handleUpdate); return () => { socketService.removeMessageHandler('update', handleUpdate); }; }, [handleUpdate]); }; ``` ### Debounced Reordering Pattern ```typescript const useReordering = () => { const debouncedPersist = useMemo( () => debounce(async (items: Item[]) => { try { await api.updateOrder(items); } catch (error) { console.error('Failed to persist order:', error); // Rollback or retry logic } }, 500), [] ); const handleReorder = useCallback((dragIndex: number, dropIndex: number) => { // Update UI immediately setItems(prev => { const newItems = [...prev]; const [draggedItem] = newItems.splice(dragIndex, 1); newItems.splice(dropIndex, 0, draggedItem); // Update order numbers return newItems.map((item, index) => ({ ...item, order: index + 1 })); }); // Persist changes (debounced) debouncedPersist(items); }, [items, debouncedPersist]); }; ``` ## WebSocket Service Configuration ### Deduplication The enhanced WebSocketService now includes automatic deduplication: ```typescript // Configure deduplication window (default: 100ms) socketService.setDeduplicationWindow(200); // 200ms window // Duplicate messages within the window are automatically filtered ``` ### Connection Management ```typescript // Always check connection state before critical operations if (socketService.isConnected()) { socketService.send({ type: 'update', data: payload }); } // Monitor connection state socketService.addStateChangeHandler((state) => { if (state === WebSocketState.CONNECTED) { console.log('Connected - refresh data'); } }); ``` ## Common Patterns ### 1. State Equality Checks Always check if incoming data actually differs from current state: ```typescript // ❌ BAD - Always triggers re-render setTasks(prev => prev.map(t => t.id === id ? newTask : t)); // ✅ GOOD - Only updates if changed setTasks(prev => { const existing = prev.find(t => t.id === id); if (existing && deepEqual(existing, newTask)) return prev; return prev.map(t => t.id === id ? newTask : t); }); ``` ### 2. Modal State Handling Be aware of modal state when applying updates: ```typescript const handleSocketUpdate = useCallback((data) => { if (isModalOpen && editingItem?.id === data.id) { console.warn('Update received while editing - consider skipping or merging'); // Option 1: Skip the update // Option 2: Merge with current edits // Option 3: Show conflict resolution UI } // Normal update flow }, [isModalOpen, editingItem]); ``` ### 3. Cleanup Pattern Always clean up socket listeners: ```typescript useEffect(() => { const handlers = [ { event: 'create', handler: handleCreate }, { event: 'update', handler: handleUpdate }, { event: 'delete', handler: handleDelete } ]; // Add all handlers handlers.forEach(({ event, handler }) => { socket.addMessageHandler(event, handler); }); // Cleanup return () => { handlers.forEach(({ event, handler }) => { socket.removeMessageHandler(event, handler); }); }; }, [handleCreate, handleUpdate, handleDelete]); ``` ## Performance Tips 1. **Measure First**: Use React DevTools Profiler before optimizing 2. **Batch Updates**: Group related state changes 3. **Debounce Rapid Changes**: Especially for drag & drop operations 4. **Use Stable References**: Memoize callbacks passed to child components 5. **Avoid Deep Equality Checks**: Use optimized comparison for large objects ## Debugging Enable verbose logging for troubleshooting: ```typescript // In development if (process.env.NODE_ENV === 'development') { console.log('[Socket] Message received:', message); console.log('[Socket] Deduplication result:', isDuplicate); console.log('[Optimistic] Pending updates:', pendingUpdates); } ``` ## Migration Guide To migrate existing components: 1. Import `useOptimisticUpdates` hook 2. Wrap socket handlers with `useCallback` 3. Add optimistic update tracking to local changes 4. Check for pending updates in socket handlers 5. Test with React DevTools Profiler Remember: The goal is to eliminate unnecessary re-renders while maintaining real-time synchronization across all connected clients.