component-refactor
Break down large React components, extract hooks, and improve component architecture. Use when components exceed 300 lines, have too many responsibilities, or need restructuring.
When & Why to Use This Skill
This Claude skill streamlines React development by automating the refactoring of complex, monolithic components into modular and maintainable architectures. It specializes in extracting custom hooks, implementing advanced design patterns, and improving overall code readability and performance according to industry best practices.
Use Cases
- Deconstructing monolithic React components exceeding 300 lines into smaller, reusable subcomponents to improve maintainability.
- Extracting complex state logic and side effects into custom hooks to decouple business logic from UI rendering.
- Implementing the Compound Components pattern to eliminate 'prop explosion' and simplify component APIs.
- Restructuring deeply nested JSX and conditional logic using flexible patterns like 'Children as a Function' or 'Render Props'.
- Improving code testability and performance by establishing clear component boundaries and reducing unnecessary re-renders.
- Organizing project directories and exports to ensure a scalable and professional frontend architecture.
| name | component-refactor |
|---|---|
| description | Break down large React components, extract hooks, and improve component architecture. Use when components exceed 300 lines, have too many responsibilities, or need restructuring. |
Component Refactoring Guide
When to Use
- Component exceeds 300 lines
- Component has more than 5-7 useState calls
- Multiple unrelated features in one component
- Prop drilling more than 2 levels deep
- Difficult to test or reason about
- Performance issues from re-renders
Quick Decision Tree
Is component > 300 lines?
├── Yes → Consider splitting
│ ├── Multiple UI sections? → Extract subcomponents
│ ├── Complex state logic? → Extract custom hook
│ └── Shared behavior? → Extract utility function
└── No
├── Too many props (>7)? → Use composition or context
└── Hard to test? → Extract logic to hooks
Refactoring Patterns
1. Extract Subcomponents
Before: Monolithic component
function JournalPage() {
const [entries, setEntries] = useState([]);
const [filter, setFilter] = useState('all');
const [search, setSearch] = useState('');
// ... 50 more lines of state and handlers
return (
<div>
{/* Header - 50 lines */}
<header className="...">
<h1>Journal</h1>
<input value={search} onChange={...} />
<select value={filter} onChange={...}>...</select>
</header>
{/* Entry List - 100 lines */}
<div className="grid">
{entries.map(entry => (
<div key={entry.id} className="...">
{/* 50 lines of entry card markup */}
</div>
))}
</div>
{/* Composer - 80 lines */}
<form onSubmit={...}>
{/* Complex form markup */}
</form>
</div>
);
}
After: Composed components
function JournalPage() {
const [entries, setEntries] = useState([]);
const [filter, setFilter] = useState('all');
const [search, setSearch] = useState('');
const filteredEntries = useMemo(() =>
entries.filter(e => matchesFilter(e, filter, search)),
[entries, filter, search]
);
return (
<div>
<JournalHeader
search={search}
onSearchChange={setSearch}
filter={filter}
onFilterChange={setFilter}
/>
<EntryGrid entries={filteredEntries} />
<JournalComposer onSubmit={handleSubmit} />
</div>
);
}
// components/journal/journal-header.tsx
function JournalHeader({ search, onSearchChange, filter, onFilterChange }) {
return (
<header className="...">
<h1>Journal</h1>
<SearchInput value={search} onChange={onSearchChange} />
<FilterSelect value={filter} onChange={onFilterChange} />
</header>
);
}
// components/journal/entry-grid.tsx
function EntryGrid({ entries }) {
return (
<div className="grid">
{entries.map(entry => (
<EntryCard key={entry.id} entry={entry} />
))}
</div>
);
}
2. Extract Custom Hooks
Before: Logic mixed with UI
function WorkflowCanvas() {
const [nodes, setNodes] = useState([]);
const [edges, setEdges] = useState([]);
const [selectedNode, setSelectedNode] = useState(null);
const [isDragging, setIsDragging] = useState(false);
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
const handleNodeDragStart = (nodeId, e) => {
setIsDragging(true);
setSelectedNode(nodeId);
setDragOffset({
x: e.clientX - nodes.find(n => n.id === nodeId).x,
y: e.clientY - nodes.find(n => n.id === nodeId).y,
});
};
const handleNodeDrag = (e) => {
if (!isDragging) return;
setNodes(nodes.map(n =>
n.id === selectedNode
? { ...n, x: e.clientX - dragOffset.x, y: e.clientY - dragOffset.y }
: n
));
};
// ... 200 more lines of drag/drop, edge connection, etc.
return (
<div onMouseMove={handleNodeDrag}>
{/* Canvas rendering */}
</div>
);
}
After: Logic in hook
// hooks/use-canvas-interactions.ts
function useCanvasInteractions(initialNodes, initialEdges) {
const [nodes, setNodes] = useState(initialNodes);
const [edges, setEdges] = useState(initialEdges);
const [selectedNode, setSelectedNode] = useState(null);
const { isDragging, handlers: dragHandlers } = useNodeDrag(nodes, setNodes);
const { isConnecting, handlers: connectHandlers } = useEdgeConnect(edges, setEdges);
return {
nodes,
edges,
selectedNode,
setSelectedNode,
isDragging,
isConnecting,
handlers: {
...dragHandlers,
...connectHandlers,
},
};
}
// hooks/use-node-drag.ts
function useNodeDrag(nodes, setNodes) {
const [isDragging, setIsDragging] = useState(false);
const [dragState, setDragState] = useState({ nodeId: null, offset: { x: 0, y: 0 } });
const handleDragStart = useCallback((nodeId, e) => {
const node = nodes.find(n => n.id === nodeId);
setIsDragging(true);
setDragState({
nodeId,
offset: { x: e.clientX - node.x, y: e.clientY - node.y },
});
}, [nodes]);
const handleDrag = useCallback((e) => {
if (!isDragging) return;
setNodes(prev => prev.map(n =>
n.id === dragState.nodeId
? { ...n, x: e.clientX - dragState.offset.x, y: e.clientY - dragState.offset.y }
: n
));
}, [isDragging, dragState, setNodes]);
const handleDragEnd = useCallback(() => {
setIsDragging(false);
setDragState({ nodeId: null, offset: { x: 0, y: 0 } });
}, []);
return {
isDragging,
handlers: { handleDragStart, handleDrag, handleDragEnd },
};
}
// Component is now clean
function WorkflowCanvas({ initialNodes, initialEdges }) {
const canvas = useCanvasInteractions(initialNodes, initialEdges);
return (
<div
onMouseMove={canvas.handlers.handleDrag}
onMouseUp={canvas.handlers.handleDragEnd}
>
{canvas.nodes.map(node => (
<CanvasNode
key={node.id}
node={node}
isSelected={node.id === canvas.selectedNode}
onDragStart={canvas.handlers.handleDragStart}
onClick={() => canvas.setSelectedNode(node.id)}
/>
))}
</div>
);
}
3. Compound Components Pattern
Before: Prop explosion
<Modal
isOpen={isOpen}
onClose={onClose}
title="Edit Entry"
showCloseButton={true}
footer={<Button onClick={save}>Save</Button>}
headerExtra={<Badge>Draft</Badge>}
size="large"
// ... 10 more props
>
{content}
</Modal>
After: Compound components
<Modal isOpen={isOpen} onClose={onClose} size="large">
<Modal.Header>
<Modal.Title>Edit Entry</Modal.Title>
<Badge>Draft</Badge>
<Modal.CloseButton />
</Modal.Header>
<Modal.Body>
{content}
</Modal.Body>
<Modal.Footer>
<Button onClick={save}>Save</Button>
</Modal.Footer>
</Modal>
4. Render Props / Children as Function
Before: Complex conditional rendering
function DataLoader({ url, render }) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => { /* fetch logic */ }, [url]);
if (loading) return <Spinner />;
if (error) return <ErrorDisplay error={error} />;
return render(data);
}
// Usage is inflexible
<DataLoader url="/api/data" render={(data) => <DataView data={data} />} />
After: Flexible children pattern
function DataLoader({ url, children }) {
const { data, loading, error, refetch } = useFetch(url);
return children({ data, loading, error, refetch });
}
// Usage is flexible
<DataLoader url="/api/data">
{({ data, loading, error, refetch }) => (
<>
{loading && <Spinner />}
{error && <ErrorBanner error={error} onRetry={refetch} />}
{data && <DataView data={data} />}
</>
)}
</DataLoader>
File Organization
components/
├── journal/
│ ├── index.ts # Exports
│ ├── journal-page.tsx # Main container
│ ├── journal-header.tsx # Header subcomponent
│ ├── journal-composer.tsx # Composer subcomponent
│ ├── entry-grid.tsx # List/grid view
│ └── entry-card.tsx # Single entry card
│
├── workflow/
│ ├── index.ts
│ ├── workflow-canvas.tsx # Main canvas
│ ├── canvas-node.tsx # Single node
│ ├── canvas-edge.tsx # Connection line
│ └── node-types/ # Node type components
│ ├── rectangle-node.tsx
│ ├── diamond-node.tsx
│ └── index.ts
Refactoring Checklist
- Identify the primary responsibility
- Extract unrelated UI into subcomponents
- Extract complex state logic into custom hooks
- Move shared utilities to lib/utils
- Update imports to use index exports
- Add/update tests for extracted pieces
- Verify no regression in functionality
Warning Signs
| Smell | Solution |
|---|---|
| File > 500 lines | Split into multiple components |
| > 7 useState calls | Extract to custom hook |
| > 7 props | Use composition or context |
| Deeply nested JSX | Extract subcomponents |
| Duplicated logic | Extract to shared hook |
| Hard to name component | It's doing too much |
See Also
- patterns.md - More refactoring patterns
- checklist.md - Pre-refactor checklist