# Extract Hooks

> Audit TSX files, identify non-UI logic, and extract it into custom hooks. Use when the user invokes /extract-hooks, or says "extract hooks", "move logic out of components", or "separate logic from layout".

- Skill: `rosslevinsky/extract-hooks` (Agent Skill)
- Install (CLI): `npx skillmds@latest add rosslevinsky/extract-hooks`
- Raw SKILL.md: https://api.skillmd.com/api/skills/rosslevinsky/extract-hooks/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Security
- Author: rosslevinsky (https://skillmd.com/u/rosslevinsky)
- Updated: 2026-09-21
- Page: https://skillmd.com/skills/rosslevinsky/extract-hooks

---


# 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.

1. Find all `.tsx` files. 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 `.tsx` file (duplication)

2. Find the existing hooks directory. Note whether it exists and where it should be created if not, based on project structure conventions.

3. List any existing `use*.ts` or `use*.tsx` files and what they do — do not duplicate them.

4. **Present the inventory to the user** as a table before proceeding. If no `.tsx` file 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 `.tsx` files 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:

1. **Define the hook API** before writing any code:
   - Inputs (arguments)
   - Returned state values
   - Returned actions/handlers
   - Required side effects

2. **Name hooks after the domain behavior**, not the component they came from (e.g. `useVoting`, not `useRoomDetailVoting`).

3. **Extract the logic** from the source `.tsx` file(s) into the hooks directory. Preserve exact behavior — do not refactor or rename while moving.

4. **Update the `.tsx` file(s)** to call the hook and remove the extracted code. The component should be left with only layout, JSX, and hook calls — no raw `useState`/`useEffect`/API calls.

5. **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

6. **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.md` conventions (whichever exists).

