Skip to main content
Ai promptsRefactor promptsReactHooks

Write a React Class to Hooks Refactor Prompt That Preserves Behavior

Build a React class to hooks refactor prompt that maps lifecycle to useEffect deps, flags stale closures, and locks a behavior-preserving contract.

PPromptsCart Team·August 11, 2026·Updated August 11, 2026·7 min read

Converting a React class component to hooks looks mechanical. this.state becomes useState, componentDidMount becomes a useEffect. The model gets that part right almost every time. Then it gets the dependency array wrong, or it introduces a stale closure the class version never had, and now you've got a subtle bug that passes review because the diff looks correct. A React class to hooks refactor prompt exists to catch exactly that 20%.

The tutorials online stop at the happy path. They show a counter or a contacts list converting cleanly and call it done. Real class components carry setState callbacks, instance variables, and lifecycle logic that doesn't map one-to-one. That's where a reusable prompt with a behavior-preserving contract earns its place.

Why the easy conversions hide the hard bugs

Hooks and classes look interchangeable until they aren't. A class always reads the freshest this.state. A hook reads whatever the closure captured when it ran. Convert a component without thinking about that, and an event handler fires with stale data, intermittently, in a way that's miserable to reproduce.

The opinion here, and it's not the default the model will give you: most class-to-hooks bugs aren't in the hooks. They're in the dependency arrays. An empty [] is not automatically componentDidMount, and a missing dependency is a stale value waiting to happen. A refactor prompt that doesn't force explicit, justified dependency arrays is just moving the bug from a class into a hook.

What a React class to hooks refactor prompt does

A React class to hooks refactor prompt is a behavior-preserving converter that maps each lifecycle method to a specific useEffect, turns this.state into named useState calls, and flags every place where hooks change runtime behavior. It converts the mechanical parts and quarantines the rest.

Run it to:

  • Map this.state to named useState calls, one per logical slice
  • Convert each lifecycle method to a useEffect with explicit deps
  • Translate setState callbacks into the functional updater form
  • Flag stale-closure risks in handlers and effects for review
  • Move instance variables to useRef where they shouldn't trigger renders
  • Emit a behavior-difference note so you know what changed

That behavior-difference note is the whole point. Without it, you're trusting a diff that looks right.

Anatomy of the prompt

Variables
  {{class_component}}   → the class component to convert
  {{external_state}}    → context/redux the component reads (for closures)
  {{react_version}}     → target React version

Prompt
  Role: you are a React class-to-hooks refactoring engineer.
  Task: convert {{class_component}} preserving behavior exactly.
  Rules:
    - Each useEffect names its dependency array explicitly.
    - Convert setState callbacks to functional updaters.
    - Flag stale-closure risks; never silently rewrite them.

Output contract
  Return: hooks component + dependency rationale + behavior-diff list.

1. Give it the surrounding state

A stale closure depends on what the component reads. Paste the relevant context or store access into {{external_state}} so the model can spot a value that'll go stale.

2. Demand explicit dependency arrays

The contract requires every useEffect to name its dependencies and say why. An effect with an unexplained empty array is a flag, not a pass.

3. Read the behavior-diff list first

Before the code, read what the model says changed. That's where the genuine review lives: the setState callback that became a functional updater, the handler that might capture stale state.

The dependency array is where behavior changes

The single most common class-to-hooks regression is a wrong dependency array. Too few dependencies and the effect reads stale values; too many and it fires constantly. A refactor prompt that lets the model emit [] without justifying it is hiding the exact place the behavior shifted. Force a one-line rationale per effect, and the risky conversions announce themselves.

Prompt-craft patterns for hooks conversion

Make dependency arrays explicit and justified. A useEffect with an unexplained dependency array is the bug surface. The contract should require a reason for each entry, so a missing dependency is visible instead of silent.

Convert setState callbacks to functional updaters. this.setState(prev => ...) doesn't translate to a direct state set. A prompt that ignores this introduces races. Name the functional-updater rule so the model uses setX(prev => ...).

Quarantine stale-closure risks. Any value read inside an effect or handler that could go stale gets flagged, not rewritten. The model can't always tell, so the safe move is to surface it for a human.

Where Claude and ChatGPT diverge on dependencies

The two models differ most on dependency arrays. Claude tends to populate them explicitly and comment why each entry is there, which makes the behavior change auditable in review. ChatGPT writes cleaner-looking hooks but leaves arrays incomplete or empty unless the "name every dependency and justify it" rule is the last thing it reads.

Both models miss stale closures by default, because spotting one needs runtime reasoning they don't do unprompted. So the contract has to force the flag. There's a related trap with useRef: an instance variable that shouldn't trigger re-renders belongs in a ref, and a model that converts it to useState will cause extra renders. Restate the dependency contract on the final line for ChatGPT; Claude holds it from a dedicated heading. Pin the model version, because a converter that started leaving dependencies out after an update is a regression you'll chase through flaky behavior.

How this connects to broader refactors

A class-to-hooks pass is a behavior-preserving refactor, the same category as porting code between languages or upgrading a framework. The discipline of locking an output contract per unit applies across all of them. For language-level moves, the cross-language refactor prompt covers idiom preservation; for a full version bump around the component, the framework migration prompt handles staged rollout.

Variables you'll set

VariableRequiredWhat it is
{{class_component}}YesThe class component to convert
{{external_state}}RecommendedContext or store the component reads
{{react_version}}YesTarget React version

Getting started

  1. Copy the structure into ChatGPT, Claude, or Gemini.
  2. Paste one class component into {{class_component}}.
  3. Add the context or store access in {{external_state}}.
  4. Run it and read the behavior-diff list before the code.
  5. Verify each useEffect dependency array against the rationale.
  6. Resolve the flagged stale-closure risks by hand.

For converting many components with the behavior checks built in, the Cross-Language Refactor Harness runs the contract-locked pass at scale.

Browse the refactor prompt packs
Skip the setup

The Cross-Language Refactor Harness does this end-to-end: it locks an output contract per file and flags constructs that don't translate cleanly, so a class-to-hooks pass surfaces every stale-closure and dependency-array risk instead of hiding them in a clean-looking diff. It's part of The Complete AI Prompts Bundle, a one-time lifetime license to the whole catalog plus future packs, worth it if you refactor more than one codebase a year.

Get the Cross-Language Refactor Harness

After converting, you'll want to confirm the hooks version behaves identically under test. The Framework Migration Harness Playbook stages that kind of verify-before-proceed check across a React version bump. New to buying packs versus rolling your own? Start with how to choose a reusable AI prompt pack.

See the Framework Migration Harness Playbook
FAQ

Common questions

Why not just ask ChatGPT to convert a class component to hooks?
A one-shot 'convert this to hooks' request gets the easy 80% right and the dangerous 20% wrong. A React class to hooks refactor prompt maps each lifecycle method to a specific `useEffect` with an explicit dependency array, converts `this.state` to named `useState` calls, and flags the parts that change behavior, like setState callbacks and stale closures, instead of silently rewriting them.
What breaks when you convert lifecycle methods to useEffect?
The most common break is a stale closure: a value read inside `useEffect` or an event handler captures the render it was created in, where the class version always saw the latest `this.state`. The second is dependency arrays: an empty array isn't always `componentDidMount`. A good refactor prompt names each effect's dependencies explicitly and routes anything ambiguous to a review list.
Does Claude or ChatGPT handle hooks conversion better?
Claude tends to add explicit dependency arrays and comment the lifecycle mapping inline, which makes the behavior change auditable. ChatGPT writes terser hooks but will leave dependency arrays incomplete unless the 'name every effect dependency' rule sits at the end of the prompt. Both miss stale closures by default, so the contract has to force a flag. Pin the model version for anything you depend on.
Stop reading. Start shipping.

Get the prompt packs this guide is built on

Ready-to-paste prompts with documented variables and usage guides for ChatGPT, Claude, and Gemini. One-time payment, own it forever.