React Hooks have been the recommended way to write React components since 2019. Yet countless codebases still have hundreds of class components that nobody wants to convert. The task is mechanical, tedious, and easy to defer - which is exactly why it never gets done.
AI agents excel at exactly this kind of work: pattern-based transformations across many files, where the rules are clear but the volume is overwhelming.
Why Class-to-Hooks Migrations Stall
Every team with legacy React code has the same conversation:
"We should convert these class components to hooks." "Yeah, but we have 200 of them." "We'll do it incrementally."
Six months later, you have 195 class components and 5 converted ones that someone did during a slow week.
The problem isn't that hooks are hard. It's that converting each component requires:
- Understanding the component's lifecycle methods
- Translating state to useState calls
- Converting lifecycle methods to useEffect
- Handling refs with useRef
- Testing that behavior is preserved
- Updating any code that relies on refs to the component
Multiply that by 200 components, and you're looking at days of mind-numbing work.
The Automated Approach
Instead of converting one component at a time, describe the scope:
@devonair migrate all class components in /src/components to functional components with hooks
The agent handles the mechanical transformation:
this.statebecomesuseStatecallscomponentDidMountbecomesuseEffectwith empty dependency arraycomponentDidUpdatebecomesuseEffectwith appropriate dependenciescomponentWillUnmountcleanup moves touseEffectreturn functionsthis.refsbecomesuseRef- Method bindings are eliminated (no more
this.handleClick = this.handleClick.bind(this))
Migration Patterns
The Full Conversion
Convert everything in one pass:
@devonair convert all class components to functional components with hooks across the entire /src directory
Best for: Smaller codebases, comprehensive test coverage, teams ready to review large PRs.
The Directory-by-Directory Approach
Convert one area at a time:
@devonair convert class components to hooks in /src/components/forms
Review, merge, then continue:
@devonair convert class components to hooks in /src/components/layout
Best for: Larger codebases, wanting smaller PRs, limited test coverage.
The Priority-Based Approach
Start with the components you change most often:
@devonair convert the UserProfile, Dashboard, and Settings class components to hooks
The most-touched code benefits most from modernization.
What the Agent Handles
State Conversion
Class component state:
class Counter extends React.Component {
state = { count: 0, name: '' };
increment = () => {
this.setState({ count: this.state.count + 1 });
}
}
Becomes:
function Counter() {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
const increment = () => {
setCount(count + 1);
};
}
The agent separates state into individual useState calls for better granularity.
Lifecycle Methods
The agent maps lifecycle methods to the appropriate hooks:
componentDidMount→useEffect(() => {}, [])componentDidUpdate→useEffect(() => {}, [deps])componentWillUnmount→useEffect(() => { return cleanup }, [])shouldComponentUpdate→React.memo()getDerivedStateFromProps→useStatewith update logic
Method Binding
No more constructor binding or arrow function properties:
// Class approach
constructor() {
this.handleClick = this.handleClick.bind(this);
}
// Hooks approach - just use the function
const handleClick = () => { ... };
Refs
// Class
this.inputRef = React.createRef();
<input ref={this.inputRef} />
// Hooks
const inputRef = useRef();
<input ref={inputRef} />
Handling Complex Conversions
Multiple Lifecycle Methods
Components with several lifecycle methods need careful useEffect mapping:
@devonair convert UserDashboard to hooks, ensuring all lifecycle methods map correctly to useEffect calls
The agent creates separate useEffect hooks for logically distinct behaviors rather than cramming everything into one.
Context Consumers
Class components using context need conversion to useContext:
@devonair convert components using ThemeContext.Consumer to useContext(ThemeContext)
Higher-Order Components
HOCs wrapping class components can often become custom hooks:
@devonair convert the withAuth HOC pattern to a useAuth custom hook
Error Boundaries
Note: Error boundaries must remain class components. React doesn't have a hooks equivalent yet.
@devonair convert all class components to hooks except error boundaries
Testing After Migration
Conversion should preserve behavior, but verify:
@devonair run the test suite and fix any failures after the hooks migration
@devonair update snapshot tests to reflect the converted components
Watch for:
- Timing differences (useEffect runs after render, lifecycle methods have different timing)
- State batching differences
- Ref access timing
Maintaining Hook Quality
Once converted, keep the codebase modern:
@devonair on PR: if any new class components are added, flag for review
@devonair schedule weekly: report any remaining class components in /src
When to Keep Class Components
Some scenarios still warrant class components:
- Error boundaries: Required to be classes
- Legacy library integration: Some libraries expect class component refs
- Complex state machines: Sometimes
useReducerisn't enough
The goal isn't religious adherence to hooks - it's cleaner, more maintainable code.
A Complete Migration Plan
Step 1: Inventory
@devonair list all class components in /src with their file paths and complexity
Step 2: Test coverage
@devonair identify class components with less than 50% test coverage
Add tests for high-risk components before converting.
Step 3: Simple components first
@devonair convert class components that only use state (no lifecycle methods)
Step 4: Lifecycle components
@devonair convert class components using componentDidMount and componentWillUnmount
Step 5: Complex components
@devonair convert remaining class components with componentDidUpdate
Step 6: Verification
@devonair run full test suite and verify no regressions
Getting Started
Pick a component - something with state and maybe a lifecycle method, but not your most complex one:
@devonair convert the Sidebar class component to a functional component with hooks
Review the conversion. Does the logic make sense? Do tests pass?
Then expand:
@devonair convert all class components in /src/components/navigation to hooks
That migration you've been putting off? It can start today with a single prompt.
FAQ
Will hooks affect my bundle size?
Hooks typically result in slightly smaller components because they eliminate class boilerplate. The difference is usually negligible.
How do I handle getDerivedStateFromProps?
The agent typically converts this to a combination of useState and an update effect, or refactors to compute the value during render. Review these conversions carefully.
What about components using forwardRef?
The agent preserves forwardRef wrapper and converts the inner component to use hooks with useImperativeHandle if needed.
Can I convert components that use Redux connect()?
Yes. The agent can also modernize these to use useSelector and useDispatch:
@devonair convert class components using connect() to use hooks with useSelector and useDispatch