---
trigger: glob
globs: 'app/hooks/**/*.ts,app/hooks/**/*.tsx,app/**/hooks/*.ts'
---
# React Hooks Rules

## Core Principles

**Single Responsibility:** One hook, one concern.
**Compose, don't combine:** Multiple focused hooks > one mega-hook.
**Stable references:** Use refs for values, avoid recreating objects.

## Real Example from Codebase

See @useAudioPlayer.ts for hook composition:

- @useVolumeControl.ts - Volume & mute logic
- @useHowlLifecycle.ts - Instance management
- @usePlaybackControl.ts - Play/pause control

Each focused hook < 100 lines, composed in main hook.

## Hook Composition Patterns

### ✅ Good: Focused Hooks

```typescript
const volumeControl = useVolumeControl({ initialVolume });
const lifecycle = useHowlLifecycle({ src, loop });
const playback = usePlaybackControl({ howl: lifecycle.howl });

return { ...volumeControl, ...playback };
```

### ❌ Bad: Mega Hook

```typescript
// One hook doing everything - 200+ lines, 5+ useEffects
function useAudioPlayer() {
  // volume + lifecycle + playback all tangled
}
```

## XState Performance

**Use `useActorRef` + `useSelector` over `useMachine`:**

See @useShopDiscovery.ts for implementation:

```typescript
// ✅ Performance-optimized
const actorRef = useActorRef(machine);
const isReady = useSelector(actorRef, selectIsReady);

// ❌ Re-renders on any state change
const [state, send] = useMachine(machine);
```

**Always create selectors file** - see @discovery.selectors.ts

## Effect Stability

### Unstable Dependencies Are Code Smells

```typescript
// ❌ BAD: howl is new object every render
useEffect(() => {
  if (howl) howl.volume(targetVolume);
}, [howl, targetVolume]);

// ✅ GOOD: Stable ref (see @useAudioPlayer.ts:47-51)
const howlRef = useRef(howl);
useEffect(() => {
  howlRef.current = howl;
}, [howl]);
useEffect(() => {
  if (howlRef.current) howlRef.current.volume(targetVolume);
}, [targetVolume]);
```

## Async Coordination

### Event-Driven > Timers

See @helpers.ts implementation:

```typescript
// ❌ BAD: setTimeout (memory leaks, race conditions)
setTimeout(() => cleanup(), 3000);

// ✅ GOOD: Library events
howl.on("fade", () => {
  cleanup();
  onComplete();
});
```

Always cleanup event listeners to prevent memory leaks.

### Cleanup Checklist

Always clean up in effect return:

- Event listeners (`removeEventListener`)
- Timers (`clearTimeout`, `clearInterval`)
- Subscriptions (unsubscribe functions)
- Network requests (abort controllers)

## When to Split a Hook

Split if hook has 3+ of these:

- Multiple `useEffect` blocks (>3)
- Multiple unrelated state pieces
- Line count > 100
- Difficult to name (has "and")
- Multiple reasons to change

## Hook Organization

```
feature/
  useFeature.ts          # Main orchestrator
  useFeatureState.ts     # State management
  useFeatureEffects.ts   # Side effects
  feature.machine.ts     # XState (if complex)
  feature.selectors.ts   # XState selectors
```
