Skip to content

[Refactoring] Decompose OrdersTable into Focused Components and Hooks #276

@syed-reza98

Description

@syed-reza98

Problem

The orders-table.tsx component contains 981 lines and manages multiple complex concerns in a single file:

  1. Real-time update system: SSE + polling fallback with connection management
  2. Data fetching: Orders API calls with complex filtering
  3. Table rendering: TanStack Table with sorting, pagination
  4. Filter management: Search, status, payment status, date range
  5. Update mode UI: Dropdown with 5 different modes (SSE, polling intervals, manual)
  6. Connection status: Real-time indicator with icons and tooltips
  7. Order operations: View, cancel, download invoice actions
  8. Responsive design: Mobile and desktop layouts

This violates the Single Responsibility Principle and creates several issues:

  • Poor testability: Testing requires mocking SSE, API calls, table logic, and UI interactions
  • Difficult maintenance: Changes to one concern affect unrelated code
  • High cognitive load: Developers must understand 8+ different responsibilities
  • Reusability limitations: Real-time update logic is locked in this component
  • Bundle size: Large client component increases initial JavaScript load

Current Code Location

  • File: src/components/orders-table.tsx (981 lines)
  • Component: OrdersTable
  • Complexity: Very High - manages SSE, API, table, filters, UI state

Proposed Refactoring

Decompose into focused, reusable components and hooks

Split the monolithic component into a layered architecture:

┌─────────────────────────────────────┐
│   OrdersTable (Container)          │  ← Orchestrates child components
│   • Minimal state coordination     │
│   • Layout composition              │
└─────────────────────────────────────┘
           ↓         ↓         ↓
    ┌──────────┐ ┌──────────┐ ┌──────────┐
    │ Filters  │ │ Updates  │ │ Actions  │
    │ Bar      │ │ Panel    │ │ Menu     │
    └──────────┘ └──────────┘ └──────────┘
           ↓
    ┌──────────────────────────┐
    │ OrdersTableView          │  ← Pure table rendering
    │ • TanStack Table only    │
    └──────────────────────────┘
           ↑
    ┌──────────────────────────┐
    │ useOrdersData Hook       │  ← Data fetching + filtering
    │ • API calls              │
    │ • Filter state           │
    │ • Pagination             │
    └──────────────────────────┘
           ↑
    ┌──────────────────────────┐
    │ useRealtimeOrders Hook   │  ← Real-time updates
    │ • SSE connection         │
    │ • Polling fallback       │
    │ • Update mode            │
    └──────────────────────────┘

Benefits

  1. Separation of Concerns: Each component/hook has a single, clear responsibility
  2. Testability: Test SSE logic, API calls, table rendering, and filters independently
  3. Reusability: useRealtimeOrders can be used in dashboard widgets, notification systems, etc.
  4. Maintainability: Changes to real-time logic don't affect table rendering
  5. Performance: Smaller components enable better React memoization
  6. Code Splitting: Table logic can be lazy-loaded for mobile views
  7. Type Safety: Focused interfaces reduce type complexity

Suggested Approach

Step 1: Extract Real-time Update Hook

Create src/hooks/useRealtimeOrders.ts:

/**
 * Manages real-time order updates with SSE + polling fallback
 * 
 * `@param` storeId - Store ID to monitor
 * `@param` onOrdersUpdated - Callback when orders change
 * `@returns` Connection state and controls
 */
export function useRealtimeOrders({
  storeId,
  onOrdersUpdated,
}: {
  storeId: string;
  onOrdersUpdated: () => void;
}) {
  // SSE connection logic
  // Polling fallback logic
  // Update mode management
  // Connection status tracking
  
  return {
    status: 'connected' | 'connecting' | 'error' | 'disconnected',
    updateMode: 'sse' | 'realtime' | 'balanced' | 'battery' | 'disabled',
    setUpdateMode: (mode) => void,
    reconnect: () => void,
  };
}

Step 2: Extract Data Fetching Hook

Create src/hooks/useOrdersData.ts:

/**
 * Manages orders data fetching with filtering and pagination
 * 
 * `@param` storeId - Store ID to fetch orders for
 * `@returns` Orders data, loading state, and filter controls
 */
export function useOrdersData({ storeId }: { storeId: string }) {
  // API call logic
  // Filter state management
  // Pagination logic
  
  return {
    orders: Order[],
    loading: boolean,
    error: Error | null,
    filters: FilterState,
    setFilters: (filters) => void,
    pagination: PaginationState,
    refetch: (silent?: boolean) => Promise(void),
  };
}

Step 3: Extract Filter Components

Create src/components/orders/filters/:

  • OrdersFilterBar.tsx - Main filter bar with search, status, dates
  • OrderStatusFilter.tsx - Status dropdown
  • PaymentStatusFilter.tsx - Payment status dropdown
  • DateRangeFilter.tsx - Date range picker

Step 4: Extract Update Mode Panel

Create src/components/orders/OrdersUpdatePanel.tsx:

/**
 * Update mode selector with connection status indicator
 */
export function OrdersUpdatePanel({
  updateMode,
  onUpdateModeChange,
  connectionStatus,
  onReconnect,
}: OrdersUpdatePanelProps) {
  // Dropdown with SSE/polling modes
  // Connection status indicator
  // Reconnect button
}

Step 5: Extract Table View

Create src/components/orders/OrdersTableView.tsx:

/**
 * Pure table rendering component
 * Only responsible for displaying orders with TanStack Table
 */
export function OrdersTableView({
  orders,
  loading,
  onSort,
  onViewOrder,
  onCancelOrder,
}: OrdersTableViewProps) {
  // TanStack Table setup
  // Column definitions
  // Row rendering
}

Step 6: Refactor Container

Simplify src/components/orders-table.tsx to orchestrate child components:

export function OrdersTable({ storeId }: { storeId: string }) {
  const { orders, loading, filters, setFilters, pagination, refetch } = 
    useOrdersData({ storeId });
    
  const { status, updateMode, setUpdateMode, reconnect } = 
    useRealtimeOrders({ storeId, onOrdersUpdated: refetch });

  return (
    (div)
      (OrdersFilterBar filters={filters} onFiltersChange={setFilters} /)
      (OrdersUpdatePanel
        updateMode={updateMode}
        onUpdateModeChange={setUpdateMode}
        connectionStatus={status}
        onReconnect={reconnect}
      /)
      (OrdersTableView
        orders={orders}
        loading={loading}
        onSort={handleSort}
        onViewOrder={handleViewOrder}
        onCancelOrder={handleCancelOrder}
      /)
    (/div)
  );
}

Code Example

Before (981 lines in one file):

export function OrdersTable({ storeId }: { storeId: string }) {
  // 40+ lines of state declarations
  const [updateMode, setUpdateMode] = useState(UpdateMode)('sse');
  const [orders, setOrders] = useState(Order[])([]);
  const [searchQuery, setSearchQuery] = useState('');
  const [statusFilter, setStatusFilter] = useState(string)('all');
  // ... 10+ more useState calls
  
  // 100+ lines of SSE logic
  const { status: sseStatus, reconnect } = useOrderStream({
    storeId,
    enabled: updateMode === 'sse',
    onNewOrders: (event) => { /* ... */ },
  });
  
  // 150+ lines of data fetching
  const fetchOrders = useCallback(async () => { /* ... */ }, []);
  
  // 200+ lines of table setup
  const columns = useMemo(ColumnDef<Order)[]>(() => [...], []);
  
  // 400+ lines of JSX with filters, indicators, table, actions
  return ( /* ... */ );
}

After (Container - ~150 lines):

import { useRealtimeOrders } from '@/hooks/useRealtimeOrders';
import { useOrdersData } from '@/hooks/useOrdersData';
import { OrdersFilterBar } from './orders/filters/OrdersFilterBar';
import { OrdersUpdatePanel } from './orders/OrdersUpdatePanel';
import { OrdersTableView } from './orders/OrdersTableView';

export function OrdersTable({ storeId }: { storeId: string }) {
  const { orders, loading, filters, setFilters, pagination, refetch } = 
    useOrdersData({ storeId });
    
  const { status, updateMode, setUpdateMode, reconnect } = 
    useRealtimeOrders({ storeId, onOrdersUpdated: refetch });

  return (
    (Card)
      (CardHeader)
        (div className="flex items-center justify-between")
          (CardTitle)Orders(/CardTitle)
          (OrdersUpdatePanel
            updateMode={updateMode}
            onUpdateModeChange={setUpdateMode}
            connectionStatus={status}
            onReconnect={reconnect}
          /)
        (/div)
      (/CardHeader)
      (CardContent)
        (OrdersFilterBar filters={filters} onFiltersChange={setFilters} /)
        (OrdersTableView
          orders={orders}
          loading={loading}
          pagination={pagination}
          onSort={handleSort}
          onViewOrder={handleViewOrder}
          onCancelOrder={handleCancelOrder}
        /)
      (/CardContent)
    (/Card)
  );
}

Impact Assessment

  • Effort: High - Requires creating 6+ new files and significant refactoring
  • Risk: Medium - Complex logic migration requires careful testing
  • Benefit: Very High - Dramatically improves maintainability, testability, and reusability
  • Priority: High - High-traffic component with ongoing feature development

Related Files

  • src/components/orders-table.tsx (primary file - 981 lines)
  • src/hooks/useOrderStream.ts (SSE hook - can be absorbed into useRealtimeOrders)
  • src/components/orders/order-filters.tsx (existing filters - needs extraction)
  • src/components/orders/orders-table-skeleton.tsx (loading state)
  • src/components/orders/cancel-order-dialog.tsx (action dialog)

Testing Strategy

  1. Unit tests: Test each hook independently with mocked dependencies
  2. Component tests: Test each UI component in isolation
  3. Integration tests: Test container composition with real hooks
  4. E2E tests: Verify SSE connection, filtering, sorting, and actions work end-to-end
  5. Performance tests: Measure render performance before/after refactoring

Migration Strategy

To minimize risk, implement incremental migration:

  1. Create new hook files alongside existing component
  2. Extract one concern at a time (filters → update panel → table view → data → real-time)
  3. Run tests after each extraction
  4. Use feature flags to toggle between old/new implementation
  5. Monitor production metrics during rollout

Additional Context

This refactoring follows Clean Architecture and Component Composition patterns. The layered approach separates:

  • Presentation (OrdersTableView, filters, panels)
  • Business Logic (useOrdersData, useRealtimeOrders hooks)
  • Infrastructure (API calls, SSE connections)

Similar patterns can be applied to other large components:

  • product-form.tsx (857 lines)
  • product-edit-form.tsx (886 lines)
  • order-detail-client.tsx (861 lines)

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

  • expires on Mar 7, 2026, 1:40 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