Extract Hooks
Move non-UI logic out of .tsx files into custom use*.ts hooks, so that .tsx files contain only layout and hook calls, without changing behavior.
Non-UI logic includes: useState, useEffect, event handlers, API calls, data fetching, data transforms, validation, derived state, and business rules.
Phase 1 — Inventory
Do not assume anything. Explore the actual codebase first.
Find all
.tsxfiles. For each, read it and record:- Total lines and approximate lines of non-UI logic vs. layout/JSX
- Categories of logic present (state, API calls, event handlers, validation, derived state, etc.)
- Whether the same or similar logic appears in another
.tsxfile (duplication)
Find the existing hooks directory. Note whether it exists and where it should be created if not, based on project structure conventions.
List any existing
use*.tsoruse*.tsxfiles and what they do — do not duplicate them.Present the inventory to the user as a table before proceeding. If no
.tsxfile contains extractable logic, report that and stop.
Phase 2 — Recommend
Propose a list of new hook files. For each hook:
- Name and signature — e.g.
useRoomForm(existingRoom?: Room): { fields, handlers, submit } - Encapsulates — what logic it extracts
- Replaces logic in — which
.tsxfiles benefit - Priority:
- High — duplicated logic across multiple files, or a single file with >100 lines of non-UI logic
- Medium — single file with 30–100 lines of non-UI logic
- Low — any non-UI logic below the Medium threshold (under 30 lines), including minor state or trivial effects; extraction is optional
Do not propose a hook that is just a thin wrapper around a single useState — only extract when it improves reuse, readability, or separates a real concern. Do not move pure helper functions into hooks; keep them as plain functions.
Present the list as a table.
Phase 3 — Confirm
After presenting the recommendation table, ask the user exactly this:
"Which hooks should I implement?
- all — implement everything above
- high / medium / low — implement by priority level
- list names — e.g.
useVoting, useRoomForm- none — stop here"
Do not proceed to Phase 4 until the user responds. If operating autonomously (no user available), default to implementing high priority hooks and note the assumption.
Phase 4 — Implement
If the hooks directory does not exist, create it at the project-appropriate path before writing any hooks.
For each approved hook, in priority order:
Define the hook API before writing any code:
- Inputs (arguments)
- Returned state values
- Returned actions/handlers
- Required side effects
Name hooks after the domain behavior, not the component they came from (e.g.
useVoting, notuseRoomDetailVoting).Extract the logic from the source
.tsxfile(s) into the hooks directory. Preserve exact behavior — do not refactor or rename while moving.Update the
.tsxfile(s) to call the hook and remove the extracted code. The component should be left with only layout, JSX, and hook calls — no rawuseState/useEffect/API calls.Correctness checks during extraction:
- Keep dependency arrays accurate
- Preserve referential stability where callers depend on it
- Watch for stale closure bugs introduced by the move
- If extraction reveals unrelated responsibilities in the same block, split into multiple hooks rather than bundling them
Verify after each hook:
- Run existing tests if available
- Run lint/typecheck if available
- Scan for changed render behavior
Phase 5 — Review
When every approved hook is implemented — not every candidate. Phase 3 lets the user
take a subset, or none, and a candidate they declined is a decision that was made, not a
gap to be closed. Review what was built; list what was declined once, without re-arguing it.
If the answer was none, the run ended at Phase 3 and there is nothing here to do.
Run the cyw skill to review the work. If the skill is unavailable, do a
self-check: re-read each modified file, confirm no logic remains inline in
.tsx files, confirm all tests/lint still pass, and list any assumptions or
risks that could not be verified locally.
Where no test exercises the logic that moved, report that hook as extracted but unverified, and name it. This skill's promise is that behaviour is preserved, and "tests still pass" is not evidence of that when nothing covered the code in the first place — a green run over untouched tests reads exactly like a checked one.
Constraints
- Move logic as-is. Do not rewrite, optimise, or rename things while extracting — that is a separate task.
- Prefer placing hooks close to the component that uses them unless the logic is clearly shared across multiple components.
- Follow the project's existing naming conventions, import style, and file structure throughout.
- Follow the project's
CLAUDE.md/AGENTS.mdconventions (whichever exists).