Skip to main content
AI
7 min read
February 1, 2026

Refactoring Legacy React Components with AI Assistance: A Walkthrough

The Component That Nobody Wants to Touch

Segev Sinay

Segev Sinay

Frontend Architect

Share:

The Component That Nobody Wants to Touch

Every codebase has one. That 800-line component with 15 useState hooks, 6 useEffect calls, inline styles mixed with CSS classes, and a comment at the top that says "TODO: refactor this." It has been there for two years. It works, somehow, and everyone is terrified to change it because the last person who tried introduced three regressions.

I refactor these components for a living. As a fractional frontend architect, half my work is walking into codebases and untangling exactly this kind of mess. AI assistance has cut my refactoring time roughly in half — not because it does the refactoring for me, but because it handles the tedious analysis and transformation steps while I focus on the architectural decisions.

Here is my complete walkthrough for refactoring a legacy React component, step by step.

Step 1: Understand Before You Touch (15 Minutes)

The biggest mistake I see is developers who start extracting hooks before they understand what the component actually does. Use this prompt:

Read src/components/Dashboard/MainDashboard.tsx

Analyze this component and give me:

1. A one-paragraph summary of what this component does from a user's perspective
2. A list of every piece of state (useState, useReducer) and what it tracks
3. A list of every side effect (useEffect) and what triggers it
4. A dependency map: which state variables affect which effects
5. A list of every API call or external interaction
6. Any implicit coupling (state A must be updated whenever state B changes)
7. Prop drilling: which props are passed through without being used
8. Any code that looks like it belongs in a different component

Format the dependency map as a simple text diagram.

This analysis gives you a mental model of the component before you change a single line. On a recent client project, this step revealed that a Dashboard component had 4 useEffect hooks that were all related to the same data-fetching lifecycle but were split apart because they were written at different times by different developers. Seeing that pattern made the refactoring plan obvious.

Step 2: Identify the Extraction Targets (10 Minutes)

Based on the analysis, identify what to extract:

Based on the analysis of MainDashboard.tsx, propose a refactoring plan.
Group the component's responsibilities into:

1. Data fetching hooks (each hook fetches one resource):
   - Name, file path, what state it manages, what API it calls

2. Business logic hooks (stateful logic unrelated to UI):
   - Name, file path, what rules it encodes

3. UI sub-components (visual sections of the component):
   - Name, file path, what props it needs, what section of JSX it renders

4. Utility functions (pure functions that can be extracted):
   - Name, file path, what it does

Rules:
- Each hook should have a single responsibility
- No hook should have more than 3 state variables
- No component should be more than 150 lines
- Maintain the same external behavior (props API stays identical)
- List any state that is shared between proposed hooks (this will need lifting)

Do NOT implement yet. Just the plan.

I always ask for the plan first. Reviewing a plan takes 2 minutes. Reviewing a botched implementation takes 2 hours.

Step 3: Extract Data Fetching Hooks First (20 Minutes)

Data fetching is the safest extraction target because it is usually self-contained. Start here:

Implement the first data fetching hook from our refactoring plan:
useDashboardStats

Extract from MainDashboard.tsx:
- The useState for stats, statsLoading, statsError
- The useEffect that fetches dashboard stats from /api/dashboard/stats
- The retry logic for failed fetches

Create the hook at src/hooks/useDashboardStats.ts

Requirements:
- Use React Query (TanStack Query) instead of raw useEffect + useState
- Return { stats, isLoading, isError, refetch }
- Add proper TypeScript types for the stats response
- Handle the case where the component unmounts during fetch
- Keep the same data shape that MainDashboard.tsx currently expects

Also update MainDashboard.tsx to use the new hook.
Show me both files.

Switching from raw useEffect to React Query during refactoring is one of the highest-value changes you can make. It eliminates race conditions, gives you caching, and reduces the amount of state management code by 60-70%.

Step 4: Extract Business Logic Hooks (15 Minutes)

Next, extract stateful logic that is not about fetching data:

Implement the business logic hook from our plan: useDashboardFilters

Extract from MainDashboard.tsx:
- The useState for dateRange, selectedMetrics, filterPreset
- The functions handleDateChange, handleMetricToggle, applyPreset, resetFilters
- The useEffect that saves filter preferences to localStorage
- The useMemo that derives filteredData from stats + current filters

Create the hook at src/hooks/useDashboardFilters.ts

Requirements:
- Accept the raw stats data as a parameter
- Return the filter state, handler functions, and filtered data
- Keep localStorage persistence logic inside the hook
- Add TypeScript types for all parameters and return values
- The hook should be reusable (no hard-coded references to dashboard)

Update MainDashboard.tsx to use this hook.
Show me both files.

Step 5: Extract UI Sub-Components (20 Minutes)

With the hooks extracted, the main component should be mostly JSX now. Split it into visual sections:

MainDashboard.tsx should now be mostly JSX with hooks at the top.
Split the JSX into sub-components:

1. DashboardHeader — title, date range picker, filter controls
   File: src/components/Dashboard/DashboardHeader.tsx
   Props: filters state and handlers from useDashboardFilters

2. StatsGrid — the grid of stat cards
   File: src/components/Dashboard/StatsGrid.tsx
   Props: stats data, isLoading

3. DashboardChart — the main chart section
   File: src/components/Dashboard/DashboardChart.tsx
   Props: chart data, selected metrics

4. ActivityFeed — the recent activity sidebar
   File: src/components/Dashboard/ActivityFeed.tsx
   Props: activities array, isLoading

Requirements:
- Each component gets its own file
- Props are explicitly typed (no spreading entire objects)
- Loading states are handled within each component (skeleton loaders)
- MainDashboard.tsx becomes a composition component (<100 lines)

Create all files and update MainDashboard.tsx.

Step 6: Verify Zero Behavioral Changes (10 Minutes)

This is the step that separates professional refactoring from cowboy coding:

Compare the original MainDashboard.tsx (from git) with the refactored version.

Verify:
1. The component accepts the same props
2. The rendered output is identical for the same data
3. All event handlers produce the same results
4. All side effects (API calls, localStorage) happen at the same times
5. Error states are handled the same way
6. Loading states are handled the same way

List any behavioral differences you find, even subtle ones.

I also run the existing test suite at this point. If tests pass, the refactoring preserved behavior. If there are no tests (which is often the case with legacy components), I write them before the next step.

The Before and After

Here is what the transformation typically looks like:

Before:

MainDashboard.tsx — 800 lines
- 15 useState hooks
- 6 useEffect hooks
- 12 handler functions
- 400 lines of JSX
- 0 custom hooks
- 0 sub-components

After:

MainDashboard.tsx — 75 lines (composition only)
useDashboardStats.ts — 35 lines
useDashboardFilters.ts — 50 lines
useDashboardChart.ts — 30 lines
DashboardHeader.tsx — 80 lines
StatsGrid.tsx — 60 lines
DashboardChart.tsx — 90 lines
ActivityFeed.tsx — 55 lines

Total lines increased from 800 to about 475. That is not a typo — when you remove duplicated state management and replace useEffect chains with React Query, you often end up with less code.

More importantly, each file has a single responsibility, each hook is testable in isolation, and the next developer who needs to change the chart section only needs to read 90 lines instead of 800.

AI
React
Productivity
TypeScript
Refactoring
Technical Leadership
Prompt Engineering

Related Articles

Contact

Let’s Connect

Have a question or an idea? I’d love to hear from you.

Send a Message