---
name: react-component-extraction
description: "Systematically extract inline components, types, and helpers from a monolithic React page component into a clean feature-directory module with barrel exports. Proven on 1000-1900 line TSX files."
version: 1.0.0
author: Hermes Agent
license: MIT
platforms: [linux, macos]
metadata:
  hermes:
    tags: [react, refactor, component-extraction, mega-component, barrel, typescript]
    related_skills: [simplify-code, codebase-audit, plan]
---

# React Component Extraction (Mega-Component Splitting)

Extract inline components, pure helpers, types, and constants from a monolithic
React page file into a `src/components/<feature>/` directory with a barrel
export, reducing the main file from 1000-1900+ lines to under 600 lines.

**When to use:** The user says "split this component," "extract the inline
components," "refactor this page," or you're facing a file over ~800 lines
with multiple inline component definitions, pure helper functions, and type
aliases all in one file.

**When NOT to use:** Single-file debugging, adding a new feature (use `plan` +
`subagent-driven-development`), small edits under 50 lines.

## Core Pattern

A React page file typically contains four categories of code that should live
separately:

| Category | Destination |
|----------|-------------|
| Pure helper functions (no hooks, no JSX) | `src/lib/<feature>.ts` |
| Type aliases, constants, localStorage helpers | `src/components/<feature>/types.ts` |
| Inline component definitions (JSX + hooks) | `src/components/<feature>/ComponentName.tsx` |
| Barrel re-exports | `src/components/<feature>/index.ts` |

The main file keeps: state hooks, effects, callbacks, CRUD handlers, filter
logic, the primary render tree, and the default export.

## Step-by-Step Process

### Phase 1 — Survey the file

Read the full file. Use `search_files` to catalog all:

- `function ComponentName` / `const ComponentName` — inline components
- `function helperName` — pure helpers (no hooks, no JSX)
- `interface` / `type` — type aliases
- `const` / `let` at module scope — constants
- `import` lines — categorize which go to which destination

Determine the dependency graph: which helpers/imports does each inline
component need? (Hook calls like `useSettings()` travel with the component.)

### Phase 2 — Create `src/lib/<feature>.ts`

Move all **pure helper functions** here. These are functions that:
- Take input, return output (no hooks, no JSX, no React API)
- Import from: `../../lib/*`, `../../types`, third-party libs
- Do NOT import from `../../store/*` (Zustand hooks belong in components)

```typescript
// Example: src/lib/repairOrders.ts
import { isVoided } from './roEvents';
import { formatDateTime } from './format';
import type { RepairOrder } from '../types';

export function calcDueTime(ro: RepairOrder): number | null { ... }
export function formatTimeRemaining(ro: RepairOrder, now: number): string { ... }
export function displayCustomerName(name?: string): string { ... }
export function normalizeRO(item: any): RepairOrder { ... }
```

Fix relative imports since the file moves from `src/pages/` to `src/lib/`:
- `../lib/foo` → `./foo`
- `../types` → `../types`

### Phase 3 — Create `src/components/<feature>/types.ts`

Move type aliases, interfaces, string-literal unions, and module-level
constants here. Also include localStorage helpers (load/persist).

```typescript
// Example: src/components/repairOrders/types.ts
export type TabId = 'active' | 'all' | 'completed';
export const TABS: { id: TabId; label: string }[] = [ ... ];

export interface SavedView { ... }
export function loadSavedViews(): SavedView[] { ... }
export function persistSavedViews(views: SavedView[]) { ... }
```

### Phase 4 — Extract each inline component

For each inline component definition, create a file
`src/components/<feature>/ComponentName.tsx`.

**Import rules for extracted components:**
- Zustand hooks (e.g. `useSettings`) import from `../../store/settings` —
  these are valid hook calls inside a component
- Pure helper functions import from `../../lib/<feature>` (or directly from
  `../../lib/roEvents` etc. if the helper wasn't moved)
- Types import from `../../types`
- Lucide icons import from `lucide-react`
- Sibling components import from `./ComponentName`

**StatusBadge pattern** — a component that takes a `config` prop AND/or
derives it from settings:
```tsx
import { useSettings } from '../../store/settings';
import { getStatusConfig } from '../../lib/status';

export function StatusBadge({ status, config }: { ... }) { ... }

// Convenience wrapper deriving config from settings
export function StatusBadgeAuto({ status }: { status: string }) {
  return <StatusBadge status={status} config={getStatusConfig(useSettings())} />;
}
```

**Memo'd components** — keep `memo()` wrapping when you extract:
```tsx
import { memo } from 'react';
export const FragmentRow = memo(function FragmentRow({ ... }) { ... });
```

**Hook calls inside extracted components:** `useSettings()`, `useNavigate()`,
etc. are valid when called inside a component that's rendered in a React tree.
The `useSettings()` call in `getStatusConfig(useSettings())` pattern is fine
at the JSX expression level — it's called on every render of the parent, and
since the parent is memo'd, that only happens on prop changes.

### Phase 5 — Create `src/components/<feature>/index.ts` (barrel)

Re-export everything the main file needs:

```typescript
export type { TabId, SavedView } from './types';
export { TABS, loadSavedViews, persistSavedViews } from './types';
export { StatusBadge } from './StatusBadge';
export { FragmentRow } from './FragmentRow';
export { CompletedRow } from './CompletedRow';
// ... all other components
export { ROModal } from './ROModal';
```

### Phase 6 — Rewrite the main file

Replace all inline definitions with imports from the barrel and lib:

```typescript
// Before: inline imports + 100s of lines of inline definitions
import { ... } from '../lib/...';
// ... 1700 lines of inline types, helpers, components, JSX

// After: imports from new locations
import {
  type TabId, TABS, TabButton, FragmentRow, CompletedRow, ROModal,
} from '../components/repairOrders';
import {
  normalizeRO, sortOrders, displayCustomerName,
} from '../lib/repairOrders';
// ... ~600 lines of main component logic + JSX
```

**Remove unused imports** — icons and types that were only used by now-extracted
components will be flagged as unused. Remove them from the main file's imports.

### Phase 7 — Verify

Run all three verification commands:

```
npm run build   → must pass (0 errors)
npm run lint    → must have 0 new warnings (pre-existing warnings are fine)
npm run test    → must be identical to before (0 regressions)
```

If lint complains:
- Remove unused imports flagged by the linter
- Fix duplicate dependency array entries
- Add missing dependencies to useCallback/useEffect arrays (from hooks that now have fewer closure captures)
- Re-run `npm run lint` and `npm run build` until clean

## Pitfalls

- **Relative import paths change.** A helper at `src/pages/RepairOrders.tsx`
  importing from `../lib/roEvents` when moved to `src/lib/repairOrders.ts`
  must import from `./roEvents`. Check every import.

- **Lib importing from components (circular).** If a lib function needs a type
  from `src/components/<feature>/types.ts`, the lib shouldn't import from
  components. Instead, define the type directly in the lib file and have the
  component's types.ts duplicate or re-export it.

- **Hook calls inline in JSX.** `getStatusConfig(useSettings())` inside a
  JSX expression is fine in an extracted component file — it's still a valid
  hook call. The convention is unusual but works with React's rules of hooks
  because it's called in the same order every render.

- **Public exports.** If the main file re-exports types (`export type { Foo } from './...'`),
  preserve the re-export path on the new index.ts so all importers keep working.

- **Unused icons from lucide-react.** After extraction, check which icons the
  main file still needs with `search_files`. Remove unused icon imports.

- **FragmentRow/CompletedRow already memo'd.** These just move file
  locations — keep the `memo()` wrapping intact.

- **Don't reorder imports or reformat.** Changing import order or whitespace
  in the main file adds noise to the diff. Only touch the lines that need to
  change.

## Verification Checklist

- [ ] `npm run build` passes (0 errors)
- [ ] `npm run lint` shows 0 new warnings
- [ ] `npm run test` — all tests pass, same count as before
- [ ] Main file line count reduced by 40-70%
- [ ] All inline components moved to individual files
- [ ] All pure helpers moved to `src/lib/<feature>.ts`
- [ ] Types/constants moved to `src/components/<feature>/types.ts`
- [ ] Barrel index.ts created
- [ ] No unused imports in main file
- [ ] No duplicate deps in dependency arrays
- [ ] No lint warnings specific to the new code

## Related

- `simplify-code` — for parallel review of recent refactoring changes (use
  *after* extraction to find cross-file duplication)
- `plan` — for writing a step plan before starting a large extraction
- `subagent-driven-development` — for dispatching extraction tasks to
  subagents in parallel
