Skip to content

[Refactoring] Extract real-time update orchestration logic from OrdersTable into custom hook #269

@syed-reza98

Description

@syed-reza98

Problem

The OrdersTable component (981 lines) contains complex real-time update orchestration logic mixing SSE, polling, and UI state that should be extracted into a reusable custom hook. This violates separation of concerns and makes the component difficult to understand and test.

Current Complexity Issues

Mixed Concerns in Component:

  • Real-time connection management (SSE vs polling modes)
  • Update mode persistence (localStorage)
  • Connection status indicators
  • Polling interval timers
  • Manual refresh triggers
  • Silent vs non-silent fetch orchestration
  • Update timestamp tracking
  • Connection fallback logic

Impact:

  • Lines 74-100: Update mode configuration and storage helpers (utility logic in component)
  • Lines 200-250: Complex SSE hook integration with callbacks and fallbacks
  • Lines 252-267: Connection indicator derivation logic
  • Lines 268-400: Fetch orchestration with silent mode, refs, and state coordination
  • Result: ~150 lines of orchestration logic mixed with presentation code

Architecture Problem

// Current: All orchestration logic in component
export function OrdersTable({ storeId }: OrdersTableProps) {
  // ❌ Update mode state and persistence
  const [updateMode, setUpdateMode] = useState(UpdateMode)('sse');
  
  // ❌ SSE integration logic
  const { status: sseStatus, reconnect: sseReconnect } = useOrderStream({...});
  
  // ❌ Polling state
  const [lastCheckedAt, setLastCheckedAt] = useState(string)(...);
  
  // ❌ Fetch orchestration
  const fetchOrders = useCallback(async (silent = false) => {...}, [...]);
  
  // ❌ Connection indicator derivation
  const getConnectionIndicator = useCallback(() => {...}, [...]);
  
  // ❌ Polling effects
  useEffect(() => { /* complex polling setup */ }, [...]);
  
  // ✅ Presentation logic (should be the only concern)
  return <Table>...</Table>;
}

Current Code Location

  • File: src/components/orders-table.tsx (981 lines)
  • Orchestration Logic: Lines 74-400 (~326 lines, 33% of component)
  • Complexity: High - Multiple state machines, effects, callbacks, refs

Proposed Refactoring

Extract all real-time orchestration logic into a dedicated useOrdersSync custom hook.

Benefits

  • Separation of Concerns: Component focuses only on presentation/UI
  • Reusability: Can reuse sync logic in other components (OrderDetail, Dashboard widgets)
  • Testability: Can unit test orchestration logic independently without mounting component
  • Maintainability: Changes to sync strategy don't require modifying component
  • Readability: Reduces component complexity from 981 lines to ~650 lines
  • Type Safety: Encapsulates sync state with proper TypeScript types

Suggested Approach

Step 1: Create dedicated custom hook

// src/hooks/useOrdersSync.ts

export type UpdateMode = 'sse' | 'realtime' | 'balanced' | 'battery' | 'disabled';

export interface UseOrdersSyncOptions {
  storeId: string;
  enabled?: boolean;
  onDataUpdate?: (orders: Order[]) => void;
  onError?: (error: Error) => void;
}

export interface UseOrdersSyncResult {
  // Connection state
  status: 'connected' | 'connecting' | 'error' | 'disconnected';
  updateMode: UpdateMode;
  lastSyncedAt: Date | null;
  
  // Actions
  setUpdateMode: (mode: UpdateMode) => void;
  forceSync: () => Promise(void);
  reconnect: () => void;
  
  // UI helpers
  connectionIndicator: {
    icon: LucideIcon;
    color: string;
    label: string;
  };
}

/**
 * Custom hook for orchestrating real-time order updates
 * Handles SSE, polling fallback, connection management, and sync state
 */
export function useOrdersSync({
  storeId,
  enabled = true,
  onDataUpdate,
  onError,
}: UseOrdersSyncOptions): UseOrdersSyncResult {
  // Internal state
  const [updateMode, setUpdateModeState] = useState(UpdateMode)(getStoredUpdateMode());
  const [lastSyncedAt, setLastSyncedAt] = useState(Date | null)(null);
  const [sseEnabled, setSseEnabled] = useState(updateMode === 'sse');
  
  // SSE integration
  const {
    status: sseStatus,
    reconnect: sseReconnect,
  } = useOrderStream({
    storeId,
    enabled: sseEnabled && enabled,
    onNewOrders: (event) => {
      // Notify parent component
      onDataUpdate?.(event.data.orders);
      setLastSyncedAt(new Date());
    },
    onStatusChange: (status) => {
      if (status === 'error') {
        // Auto-fallback to polling
        setUpdateModeInternal('balanced');
      }
    },
  });
  
  // Polling orchestration
  useEffect(() => {
    if (!enabled || updateMode === 'sse' || updateMode === 'disabled') return;
    
    const interval = UPDATE_MODE_INTERVALS[updateMode];
    const timer = setInterval(() => {
      checkForUpdates();
    }, interval);
    
    return () => clearInterval(timer);
  }, [enabled, updateMode, storeId]);
  
  // Update mode persistence
  const setUpdateMode = useCallback((mode: UpdateMode) => {
    setUpdateModeState(mode);
    setSseEnabled(mode === 'sse');
    setStoredUpdateMode(mode);
  }, []);
  
  // Force sync (manual refresh)
  const forceSync = useCallback(async () => {
    try {
      // Implementation
      const orders = await fetchOrders();
      onDataUpdate?.(orders);
      setLastSyncedAt(new Date());
    } catch (error) {
      onError?.(error as Error);
    }
  }, [storeId, onDataUpdate, onError]);
  
  // Connection indicator
  const connectionIndicator = useMemo(() => {
    if (updateMode !== 'sse') {
      return { icon: Clock, color: 'text-muted-foreground', label: 'Polling' };
    }
    switch (sseStatus) {
      case 'connected':
        return { icon: Wifi, color: 'text-green-500', label: 'Live' };
      case 'connecting':
        return { icon: Wifi, color: 'text-yellow-500 animate-pulse', label: 'Connecting...' };
      case 'error':
        return { icon: WifiOff, color: 'text-red-500', label: 'Disconnected' };
      default:
        return { icon: WifiOff, color: 'text-muted-foreground', label: 'Offline' };
    }
  }, [updateMode, sseStatus]);
  
  return {
    status: sseEnabled ? sseStatus : 'disconnected',
    updateMode,
    lastSyncedAt,
    setUpdateMode,
    forceSync,
    reconnect: sseReconnect,
    connectionIndicator,
  };
}

Step 2: Simplify OrdersTable component

// src/components/orders-table.tsx (simplified)

export function OrdersTable({ storeId }: OrdersTableProps) {
  const [orders, setOrders] = useState(Order[])([]);
  const [loading, setLoading] = useState(false);
  
  // ✅ All orchestration logic encapsulated in hook
  const {
    status,
    updateMode,
    setUpdateMode,
    forceSync,
    connectionIndicator,
  } = useOrdersSync({
    storeId,
    onDataUpdate: (newOrders) => {
      setOrders(newOrders);
      toast.info(`\$\{newOrders.length} new orders`);
    },
    onError: (error) => {
      toast.error('Failed to sync orders');
    },
  });
  
  // ✅ Component focuses on presentation
  return (
    (Card)
      (CardHeader)
        (div className="flex items-center justify-between")
          (CardTitle)Orders(/CardTitle)
          (UpdateModeSelector 
            mode={updateMode} 
            onChange={setUpdateMode}
            connectionIndicator={connectionIndicator}
          /)
        (/div)
      (/CardHeader)
      (CardContent)
        (DataTable data={orders} columns={columns} /)
      (/CardContent)
    (/Card)
  );
}

Step 3: Create comprehensive tests

// src/hooks/__tests__/useOrdersSync.test.ts

describe('useOrdersSync', () => {
  it('should use SSE by default', () => {
    const { result } = renderHook(() => useOrdersSync({ storeId: 'test' }));
    expect(result.current.updateMode).toBe('sse');
  });

  it('should fallback to polling when SSE fails', async () => {
    // Mock SSE failure
    // ...
    await waitFor(() => {
      expect(result.current.updateMode).toBe('balanced');
    });
  });

  it('should persist update mode to localStorage', () => {
    // ...
  });

  it('should call onDataUpdate when new orders arrive', () => {
    // ...
  });

  // ... more tests
});

Code Example Summary

Before: 981-line component with 326 lines of orchestration logic
After: ~30-line hook usage + 650-line focused component

Net improvement:

  • Component: 981 → 650 lines (33% reduction)
  • Reusable hook: Can be used in other components
  • Testability: Independent testing of sync logic

Impact Assessment

  • Effort: Medium - Requires careful state extraction and effect migration
  • Risk: Medium - Critical component, needs thorough testing
  • Benefit: High - Major improvement in maintainability and reusability
  • Priority: High - Component is central to dashboard functionality

Related Files

  • src/components/orders-table.tsx (lines 74-400)
  • src/hooks/useOrderStream.ts (already exists, will integrate)
  • New: src/hooks/useOrdersSync.ts

Testing Strategy

  1. Create comprehensive unit tests for useOrdersSync hook
  2. Test SSE connection scenarios (success, failure, reconnection)
  3. Test polling mode transitions
  4. Test localStorage persistence
  5. Integration test with OrdersTable component
  6. Verify real-time updates in development environment
  7. Load test with concurrent updates

Implementation Order

  1. ✅ Create useOrdersSync hook with core functionality
  2. ✅ Add unit tests for hook
  3. ✅ Create integration tests with mocked OrderStream
  4. ✅ Refactor OrdersTable to use hook
  5. ✅ Test all update modes (SSE, polling variants)
  6. ✅ Verify in staging with real SSE server
  7. ✅ Monitor production metrics after deployment

Future Enhancements

After this refactoring, useOrdersSync can be:

  • Reused in OrderDetailPage for live order status updates
  • Reused in DashboardWidget for order count badge
  • Extended to support other real-time entities (products, inventory)
  • Enhanced with offline queue and conflict resolution

AI generated by Daily Codebase Analyzer - Semantic Function Extraction & Refactoring

  • expires on Mar 5, 2026, 2:07 PM UTC

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    Backlog

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions