Goal
Modernize React code by converting class components to functional components using hooks, improving readability, testability, and runtime behavior while preserving existing UI contracts.
Rule Behavior
1️⃣ Identify Conversion Candidates
- Target components that use state, lifecycle methods, or complex render logic.
- Prefer converting small and isolated components first.
- Delay conversion of components that depend on unusual class patterns until tests exist.
2️⃣ Preserve Public Props and Behavior
- Keep prop names and defaults identical to avoid breaking calling components.
- Ensure events, callback behavior, and external API expectations remain unchanged.
3️⃣ Map Lifecycles to Hooks
Translate lifecycle methods into hook equivalents:
- componentDidMount becomes a useEffect that runs once.
- componentDidUpdate becomes a useEffect that listens to specific dependencies.
- componentWillUnmount becomes the cleanup function inside useEffect.
- setState logic becomes useState or useReducer depending on complexity.
Before
'class Counter extends React.Component {
constructor() {
super();
this.state = { count: 0 };
this.tick = this.tick.bind(this);
}
componentDidMount() {
this.timer = setInterval(this.tick, 1000);
}
componentWillUnmount() {
clearInterval(this.timer);
}
tick() {
this.setState({ count: this.state.count + 1 });
}
render() {
return <div>{this.state.count}</div>;
}
}'
After
'function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(timer);
}, []);
return <div>{count}</div>;
}'
Common Mistake: Stale State in Interval
Wrong:
'useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => clearInterval(timer);
}, []);'
Right:
'useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1);
}, 1000);
return () => clearInterval(timer);
}, []);'
4️⃣ Replace Derived State Carefully
- Convert computed values into memoized values rather than storing them directly.
- Use memoized callbacks when passing functions to children.
5️⃣ Handle Complex State with Reducers
- Use useReducer for multi-field or interdependent state structures.
- Keep the reducer pure and testable.
6️⃣ Validate Through Execution and Tests
- Run UI flows to confirm behavior, accessibility, and interactions remain identical.
- Add interaction and snapshot tests to ensure no regressions occur.
Examples
- Convert a class component to a functional one using React hooks while maintaining identical behavior.
- Replace setState logic with a reducer and verify the refactor through tests.
- Move lifecycle-dependent logic into effects and confirm correct cleanup behavior.
Tool Prompts
- Convert this class component into a functional component while keeping behavior unchanged.
- Show a step-by-step transformation explaining each lifecycle mapping.
- Run the updated component and provide logs demonstrating correct interactions.
Quick Implementation Wins
- Start with leaf-level components that have few dependencies.
- Add tests for event handling and rendering before conversion.
- Use automated codemods for basic class-to-function transformations, then refine manually.