-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Problem
Large React component files (>800 lines) mix multiple concerns in a single file, violating separation of concerns and making code harder to maintain:
-
src/app/store/[slug]/checkout/page.tsx(836 lines)- Form validation schema, UI rendering, API calls, state management, payment method handling, cart integration
-
src/components/orders-table.tsx(977 lines)- Table columns definition, SSE/polling logic, API fetching, filtering, sorting, pagination, real-time updates
-
src/components/product-edit-form.tsx(970 lines)- Form validation, variant management, image upload, API calls, UI rendering, discount handling
-
src/components/product-form.tsx(940 lines)- Nearly identical structure to product-edit-form with duplicated logic
These large components suffer from:
- Hard to understand: Developers must read 800+ lines to understand component behavior
- Difficult to test: Testing requires mocking many dependencies and covering many scenarios
- Poor reusability: Logic is tightly coupled to specific components
- Slow rendering: Large component trees with complex state management
- High cognitive load: Multiple responsibilities increase mental overhead
Current Code Location
- Files:
src/app/store/[slug]/checkout/page.tsx(836 lines)src/components/orders-table.tsx(977 lines)src/components/product-edit-form.tsx(970 lines)src/components/product-form.tsx(940 lines)
- Complexity: Very High - Multiple concerns per component
Proposed Refactoring
Decompose large components using composition patterns:
Benefits
- Single Responsibility: Each component/hook has one clear purpose
- Easier testing: Small, focused unit tests
- Better reusability: Extracted hooks and utilities can be reused
- Improved performance: Smaller components re-render less frequently
- Clearer structure: Component hierarchy documents intent
- Parallel development: Teams can work on sub-components independently
Suggested Approach: CheckoutPage (836 lines)
Phase 1: Extract Custom Hooks
// src/hooks/useCheckoutForm.ts (~100 lines)
export function useCheckoutForm(storeId: string) {
const form = useForm(CheckoutFormData)({ ... });
const handleSubmit = async (data: CheckoutFormData) => { ... };
return { form, handleSubmit, isSubmitting };
}
// src/hooks/usePaymentMethods.ts (~80 lines)
export function usePaymentMethods(storeApiUrl: string) {
const [methods, setMethods] = useState([]);
const [loading, setLoading] = useState(true);
// Fetch and manage payment methods
return { methods, loading, error };
}
// src/hooks/useDiscountCode.ts (~60 lines)
export function useDiscountCode(storeApiUrl: string) {
const [discount, setDiscount] = useState(null);
const applyCode = async (code: string) => { ... };
const removeCode = () => { ... };
return { discount, applyCode, removeCode, isApplying };
}Phase 2: Extract Sub-Components
// src/components/checkout/customer-info-section.tsx (~80 lines)
export function CustomerInfoSection({ form }) {
return (
(Card)
(CardHeader)Customer Information(/CardHeader)
(CardContent)
{/* Email, Name, Phone fields */}
(/CardContent)
(/Card)
);
}
// src/components/checkout/shipping-address-section.tsx (~100 lines)
export function ShippingAddressSection({ form }) { ... }
// src/components/checkout/billing-address-section.tsx (~100 lines)
export function BillingAddressSection({ form, showBilling }) { ... }
// src/components/checkout/payment-method-section.tsx (~120 lines)
export function PaymentMethodSection({ form, methods }) { ... }
// src/components/checkout/order-summary-section.tsx (~150 lines)
export function OrderSummarySidePanel({ cart, discount, onApplyCode }) { ... }Phase 3: Refactor Main Component
// src/app/store/[slug]/checkout/page.tsx (~200 lines)
export default function CheckoutPage() {
const { form, handleSubmit, isSubmitting } = useCheckoutForm(storeId);
const { methods, loading: methodsLoading } = usePaymentMethods(storeApiUrl);
const { discount, applyCode, removeCode } = useDiscountCode(storeApiUrl);
const { cart } = useCart();
return (
(div className="checkout-layout")
(Form {...form})
(form onSubmit={form.handleSubmit(handleSubmit)})
(CustomerInfoSection form={form} /)
(ShippingAddressSection form={form} /)
(BillingAddressSection
form={form}
showBilling={!form.watch('billingSameAsShipping')}
/)
(PaymentMethodSection form={form} methods={methods} /)
(/form)
(/Form)
(OrderSummarySidePanel
cart={cart}
discount={discount}
onApplyCode={applyCode}
onRemoveCode={removeCode}
/)
(/div)
);
}Suggested Approach: OrdersTable (977 lines)
Extract Column Definitions:
// src/components/orders/columns.tsx (~150 lines)
export function useOrdersTableColumns(): ColumnDef(Order)[] {
return [
{ id: 'orderNumber', ... },
{ id: 'customer', ... },
{ id: 'status', ... },
// ... other columns
];
}Extract Real-time Logic:
// src/hooks/useOrdersRealtime.ts (~120 lines)
export function useOrdersRealtime(storeId: string, mode: UpdateMode) {
const [orders, setOrders] = useState([]);
const { streamedOrders } = useOrderStream(storeId, mode === 'sse');
// Handle SSE vs polling logic
return { orders, refresh, connectionStatus };
}Simplified Component:
// src/components/orders-table.tsx (~350 lines)
export function OrdersTable({ storeId }) {
const columns = useOrdersTableColumns();
const { orders, refresh, status } = useOrdersRealtime(storeId, updateMode);
const table = useReactTable({ data: orders, columns, ... });
return (
(Card)
(OrdersTableHeader status={status} onRefresh={refresh} /)
(OrdersTableContent table={table} /)
(OrdersTablePagination table={table} /)
(/Card)
);
}Code Example
Before (CheckoutPage - 836 lines):
export default function CheckoutPage() {
// 100+ lines of state declarations
const [paymentMethods, setPaymentMethods] = useState([]);
const [loading, setLoading] = useState(true);
const [discount, setDiscount] = useState(null);
// ... many more states
// 200+ lines of useEffect hooks
useEffect(() => { /* fetch payment methods */ }, []);
useEffect(() => { /* validate cart */ }, []);
useEffect(() => { /* calculate totals */ }, []);
// ... many more effects
// 300+ lines of JSX with deeply nested structure
return (
(div)
{/* 300+ lines of form fields, cards, inputs */}
(/div)
);
}After (CheckoutPage - ~200 lines):
export default function CheckoutPage() {
const { form, handleSubmit } = useCheckoutForm(storeId);
const { methods } = usePaymentMethods(storeApiUrl);
const { discount, applyCode } = useDiscountCode(storeApiUrl);
return (
(CheckoutLayout)
(Form {...form})
(CustomerInfoSection form={form} /)
(ShippingAddressSection form={form} /)
(PaymentMethodSection form={form} methods={methods} /)
(/Form)
(OrderSummarySidePanel discount={discount} onApplyCode={applyCode} /)
(/CheckoutLayout)
);
}Impact Assessment
- Effort: Medium-High per component (~1-2 days each)
- Risk: Low-Medium - UI components are well-isolated, comprehensive tests mitigate risk
- Benefit: Very High - Dramatically improves code readability and maintainability
- Priority: High - Large components are a common source of bugs and slow development
Refactoring Sequence
Priority 1: CheckoutPage (836 lines)
- High user impact, complex business logic
- Extract hooks and sub-components
- Est: 2 days
Priority 2: OrdersTable (977 lines)
- Real-time complexity, many features
- Extract columns, filters, real-time logic
- Est: 2 days
Priority 3: Product Forms (940 + 970 = 1,910 lines combined)
- Extract shared logic between create/edit
- Consider unified ProductFormBase component
- Est: 3 days
Related Files
Changes per component:
- Main component file (decomposed)
- New hook files (
src/hooks/use*.ts) - New sub-component files (
src/components/[feature]/*.tsx) - Tests for extracted hooks and components
Testing Strategy
- Snapshot tests for existing components (baseline)
- Extract one hook/component at a time with unit tests
- Integration tests ensure sub-components work together
- Visual regression tests (Playwright) verify no UI changes
- Manual E2E testing: Complete user flows (checkout, viewing orders, etc.)
Additional Context
Pattern to follow (from Vercel's React best practices):
- Keep components under 250 lines
- Extract business logic into custom hooks
- Use composition over inheritance
- Colocate related components in feature folders
Other large components to consider in future:
src/components/integrations/facebook/dashboard.tsx(1,106 lines)src/components/order-detail-client.tsx(857 lines)src/components/data-table.tsx(811 lines)
Generated by Daily Codebase Analyzer - Semantic Function Extraction & Refactoring
- expires on Mar 22, 2026, 9:09 PM UTC
Metadata
Metadata
Assignees
Type
Projects
Status