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.
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.stateto nameduseStatecalls, one per logical slice - Convert each lifecycle method to a
useEffectwith explicit deps - Translate
setStatecallbacks into the functional updater form - Flag stale-closure risks in handlers and effects for review
- Move instance variables to
useRefwhere 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 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
| Variable | Required | What it is |
|---|---|---|
{{class_component}} | Yes | The class component to convert |
{{external_state}} | Recommended | Context or store the component reads |
{{react_version}} | Yes | Target React version |
Getting started
- Copy the structure into ChatGPT, Claude, or Gemini.
- Paste one class component into
{{class_component}}. - Add the context or store access in
{{external_state}}. - Run it and read the behavior-diff list before the code.
- Verify each
useEffectdependency array against the rationale. - 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 →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.
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 →Common questions
Why not just ask ChatGPT to convert a class component to hooks?
What breaks when you convert lifecycle methods to useEffect?
Does Claude or ChatGPT handle hooks conversion better?
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.
More prompt guides

Gemini vs Claude for Long-Context Code: Window or Accuracy
The honest framing of Gemini vs Claude for long-context code isn't which model is smarter. It's a tradeoff between two different things: how much code you can fit in one prompt, and how often the mode…

An OKR Drafting Prompt That Catches Vanity Key Results
An OKR drafting prompt has to fight the model's strongest instinct: handing back something that sounds like a goal but can't be measured. Ask any model for key results and you'll get "increase user en…

A Technical Design Doc Prompt That Holds the RFC Structure
A technical design doc prompt earns its keep when it stops every author from inventing a new doc structure. Context, the options you considered, why you picked one, what breaks, how you roll it out. S…