Back to Rules

🧠 Cursor Rule — Convert Class Components to Functional Components with Hooks

Official
CursorCode Navigation & Refactoring
cursorreactrefactoringmodernization

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.
View Tool Page