# Hook

> Create a reusable React hook (useX). Triggers "create hook", "new hook", "custom hook", a useX-style name, or extracting logic out of a component.

- Skill: `darkroomengineering/hook` (Agent Skill)
- Install (CLI): `npx skillmds@latest add darkroomengineering/hook`
- Raw SKILL.md: https://api.skillmd.com/api/skills/darkroomengineering/hook/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Web & Frontend
- Author: darkroomengineering (https://skillmd.com/u/darkroomengineering)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/darkroomengineering/hook

---


# Create Custom Hook

Create a custom React hook following Darkroom conventions. The hook itself is stack-agnostic — what differs between satus and novus is the path alias.

## Step 1 — Detect stack

Read `package.json`:
- `dependencies.next` → satus / Next.js (path alias `@/`, hooks require a `'use client'` boundary)
- `dependencies["react-router"]` → novus / React Router (path alias `~/`, components isomorphic)

## Step 2 — Choose location

| Stack | Hook path |
|---|---|
| satus | `lib/hooks/<name>.ts` |
| novus | `hooks/<name>.ts` (novus puts `hooks/` at the project root) |

Confirm by checking the existing `lib/hooks/` or `hooks/` directory structure if either pattern is unclear from package.json alone.

## Step 3 — Emit template

### satus / Next.js
```tsx
// lib/hooks/<name>.ts
'use client'
// Client Components can prerender on the server. Read browser APIs in effects
// or event handlers, with the same initial state on the server and browser.

import { useState, useEffect } from 'react'

interface Use<Name>Options {
  // Hook configuration options
}

interface Use<Name>Return {
  // Return type definition
}

export function use<Name>(options?: Use<Name>Options): Use<Name>Return {
  // Implementation
  return {
    // Return values
  }
}
```

### novus / React Router
```tsx
// hooks/<name>.ts
// No 'use client' — RR components are isomorphic; the hook runs wherever
// it's called from. Read browser APIs in effects or event handlers, with the
// same initial state on the server and browser.

import { useState, useEffect } from 'react'

interface Use<Name>Options {
  // Hook configuration options
}

interface Use<Name>Return {
  // Return type definition
}

export function use<Name>(options?: Use<Name>Options): Use<Name>Return {
  // Implementation
  return {
    // Return values
  }
}
```

## Conventions (both stacks)

1. **Type everything** — options interface, return interface.
2. **Named export** — `export function useX`, not default.
3. **Prefix with `use`** — React hook naming convention.
4. **No memoization** — React Compiler handles it automatically.
5. **Hydration-safe state** — both stacks can render on the server. Use a
   deterministic initial value for the server render and first browser render;
   `'use client'` does not disable prerendering. A `typeof window` branch in a
   lazy state initializer can still produce different markup and a hydration
   mismatch. Read `localStorage` in an effect, and finish that read before
   enabling an effect that persists state, so initial defaults cannot overwrite
   stored values.

## Stack-specific

| | satus | novus |
|---|---|---|
| Directive | `'use client'` (hooks live in client boundary) | None (isomorphic) |
| Browser APIs | SSR-safe initial state; effects or event handlers | SSR-safe initial state; effects or event handlers |
| Path alias | `@/` | `~/` |

## Before you start

If this hook uses an external library, **fetch docs first**:
1. Use Context7 MCP (`mcp__context7__resolve-library-id` → `get-library-docs`) in Claude or when the user configured it in standalone Codex. Otherwise use official docs through native browsing or inspect the pinned local package. cc-settings does not auto-run unpinned registry MCP packages in Codex.
2. Run `bun info <package>` to check the latest version.

## Consider Using Hamo

For common use cases, prefer `hamo` hooks (fetch `hamo` docs via Context7 first):

```tsx
import { useWindowSize, useRect, useIntersectionObserver } from 'hamo'
```

Only create custom hooks when `hamo` doesn't cover the use case.

## Example

```
User: "create a useLocalStorage hook" (in satus repo)
→ Creates lib/hooks/use-local-storage.ts with 'use client', deterministic initial state, effect-based storage read before writes

User: "create a useLocalStorage hook" (in novus repo)
→ Creates hooks/use-local-storage.ts, no directive, deterministic initial state, effect-based storage read before writes
```

## Arguments

- `$ARGUMENTS` — Hook name (e.g., "useAuth", "useLocalStorage")

