# React Component Extraction — Mega-File Split Pattern ## When you need this A single `.tsx` file has grown past ~1,000 lines with inline sub-components, inline helper functions, and inline type definitions. The established pattern is: 1. **Pure helpers → `src/lib/.ts`** — formatters, PB query builders, status label/color mappers, date formatters 2. **Types → shared module** — interfaces can stay in the page file if re-exported (for external importers), or move to `src/components//types.ts` 3. **Inline components → `src/components//`** — one file per component, barrel `index.ts` 4. **Page becomes orchestrator** — imports from barrel, keeps all state + handlers ## Proven pattern (from Appointments.tsx, Customers.tsx splits) | Extraction target | Destination | Pattern | |---|---|---| | `function HelperFn(...)` | `src/lib/.ts` | Pure function, no hooks, no JSX | | `interface X` | `src/components//types.ts` (or re-export from page) | Re-export from page if external files import from the old path | | `function Skeleton()`, `function EmptyState()` | `EmptyStates.tsx` | Plain function component | | `function Card(props): JSX` | `Card.tsx` | `memo(function NameCardImpl(props) { ... })` | | `function FormModal(props)` | `FormModal.tsx` | Keep as-is (has internal state — memo adds nothing) | | `function DetailView(props)` | `DetailView.tsx` | `memo(function DetailViewImpl(props) { ... })` | | `function DeleteConfirmModal(props)` | `DeleteConfirmModal.tsx` | Keep as-is | | Barrel | `index.ts` | Re-export all components and types | ## Import compatibility pattern When external files import types from the old page path (e.g., `import type { CustomerWithVehicles } from '../pages/Customers'`), the page MUST re-export those types: ```tsx // src/pages/Customers.tsx — at the top level export type { CustomerRecord, CustomerWithVehicles, CustomerFormData } from '../components/customers/types'; ``` Do NOT change the import paths in the 5 external files — the re-export keeps them working. ## Pitfalls discovered during extraction ### 1. Fragment shorthand → Fragment key mismatch The original file uses `<>...` (no key prop). The extracted equivalent needs ``. But the closing paren/bracket structure differs because the Fragment opening tag is part of the JSX tree rather than being a structural wrapper. **Trace template:** ``` Original: return ( <> ); After extract: return ( ); ``` The closing `));` after `` in a `.map()` callback becomes `);` after ``, then `})}` for block-end, `.map()` close, JSX expression close. **Count explicitly.** ### 2. `useCallback` dependency drift When moving a handler from the orchestrator page that references a callback defined in the same component, the `useCallback` deps array may reference a function that's now imported. The import is stable (never changes identity), so the dep IS needed in the array to be correct, but TypeScript won't warn either way. **Check:** every `useCallback(fn, deps)` where `fn` calls an imported function — that imported function should be in the deps array, or the callback is stale. ### 3. `formatDate` name collision The page likely has its own `function formatDate(...)` helper. The extracted lib module uses the same name. Rename to `formatCustomerDate` or `formatDomainDate` — the app's `src/lib/format.ts` may already export a global `formatDate`.