}>
  Lesson 4 of 8  -  Use KeyProvider and hooks to manage keyboard shortcuts in React applications.

## Overview

duck-vim's React layer wraps the core system in a context provider and exposes hooks for ergonomic usage. The provider creates a shared `Registry`, `KeyHandler`, and `SequenceManager`, attaches event listeners on mount, and cleans everything up on unmount.

***

## Setting up the provider

Wrap your app (or the relevant subtree) with `KeyProvider`:

```tsx
import { KeyProvider } from '@gentleduck/vim/react'

export default function Root() {
  return (
    <KeyProvider
      debug={process.env.NODE_ENV === 'development'}
      timeoutMs={600}
    >
      <App />
    </KeyProvider>
  )
}
```

**Props:**

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `debug` | `boolean` | `false` | Log key events and matches to console |
| `timeoutMs` | `number` | `600` | Timeout for multi-key sequences |
| `defaultOptions` | `Partial<KeyBindOptions>` | - | Default options for all bindings |

***

## `useKeyBind`: Single binding

The simplest hook. Register one binding with one handler:

```tsx
import { useKeyBind } from '@gentleduck/vim/react'

function SearchBar() {
  const [focused, setFocused] = useState(false)
  const inputRef = useRef<HTMLInputElement>(null)

  useKeyBind('/', () => {
    inputRef.current?.focus()
    setFocused(true)
  }, { ignoreInputs: true })

  useKeyBind('escape', () => {
    inputRef.current?.blur()
    setFocused(false)
  }, { enabled: focused })

  return <input ref={inputRef} placeholder="Search..." />
}
```

}>
  Key points:

  * The handler is always called with the latest closure (it's stored in a ref internally).
  * Use `enabled` to conditionally activate bindings.
  * Use `ignoreInputs: true` to skip when the user is typing in a text field.

***

## `useKeyCommands`: Bulk registration

Register multiple bindings at once with a record:

```tsx
import { useKeyCommands } from '@gentleduck/vim/react'

function Navigation() {
  const navigate = useNavigate()

  useKeyCommands({
    'g+d': {
      name: 'Go to Dashboard',
      execute: () => navigate('/dashboard'),
    },
    'g+s': {
      name: 'Go to Settings',
      execute: () => navigate('/settings'),
    },
    'g+p': {
      name: 'Go to Profile',
      execute: () => navigate('/profile'),
    },
  }, { ignoreInputs: true })

  return null // This component just registers shortcuts
}
```

}>
  **Stability warning:** The `commands` object is used in a `useEffect` dependency array. If you define it inline, it creates a new object every render and re-registers all bindings. Either define it outside the component, or wrap it in `useMemo`.

```tsx
// Good: stable reference
const commands = useMemo(() => ({
  'g+d': { name: 'Dashboard', execute: () => navigate('/dashboard') },
}), [navigate])

useKeyCommands(commands)
```

***

## `useKeySequence`: Multi-step sequences

For explicit multi-step sequences where each step can be a full key combination:

```tsx
import { useKeySequence } from '@gentleduck/vim/react'

function Editor() {
  // Press Ctrl+K, then Ctrl+C to comment
  useKeySequence(['ctrl+k', 'ctrl+c'], () => {
    commentSelection()
  })

  // Press Ctrl+K, then Ctrl+U to uncomment
  useKeySequence(['ctrl+k', 'ctrl+u'], () => {
    uncommentSelection()
  })
}
```

}>
  This is different from `useKeyCommands` with `'g+d'`:

  * `useKeyCommands` with `'g+d'` uses the Registry's sequence support (single-character steps only).
  * `useKeySequence` uses the `SequenceManager` and supports full key combinations at each step.

***

## Working without a provider

All hooks work without a `KeyProvider`. When used outside a provider, they create standalone registries and handlers internally:

```tsx
// This works even without KeyProvider
function StandaloneComponent() {
  useKeyBind('escape', () => close())
  return <div>...</div>
}
```

}>
  The tradeoff is that standalone bindings:

  * Don't appear in `registry.getAllCommands()` (they have their own registry).
  * Don't participate in sequence prefix matching with other bindings.
  * Create extra event listeners.

  Use a provider for any non-trivial app.

***

## Accessing the context directly

For advanced use cases, access the raw context:

```tsx
import { useContext } from 'react'
import { KeyContext } from '@gentleduck/vim/react'

function DebugPanel() {
  const ctx = useContext(KeyContext)
  if (!ctx) return <p>No KeyProvider found</p>

  const commands = Array.from(ctx.registry.getAllCommands())

  return (
    <div>
      <h3>Registered shortcuts ({commands.length})</h3>
      <ul>
        {commands.map(([binding, cmd]) => (
          <li key={binding}>{cmd.name}: {binding}</li>
        ))}
      </ul>
    </div>
  )
}
```

***

## Putting it together

```tsx
import { KeyProvider, useKeyBind, useKeyCommands } from '@gentleduck/vim/react'
import { useState } from 'react'

function App() {
  const [count, setCount] = useState(0)
  const [paletteOpen, setPaletteOpen] = useState(false)

  useKeyBind('Mod+k', () => setPaletteOpen(true), { preventDefault: true })
  useKeyBind('escape', () => setPaletteOpen(false), { enabled: paletteOpen })

  useKeyCommands({
    'g+i': {
      name: 'Increment',
      execute: () => setCount((c) => c + 1),
    },
    'g+r': {
      name: 'Reset',
      execute: () => setCount(0),
    },
  }, { ignoreInputs: true })

  return (
    <div className="p-8">
      <p>Count: {count}</p>
      <p className="text-sm text-gray-500">
        <Kbd>Ctrl/Cmd+K</Kbd>: palette | <Kbd>G</Kbd>,<Kbd>I</Kbd>: increment | <Kbd>G</Kbd>,<Kbd>R</Kbd>: reset | <Kbd>Escape</Kbd>: close palette
      </p>
      {paletteOpen && (
        <div className="fixed inset-0 bg-black/50 flex items-center justify-center">
          <div className="bg-white p-8 rounded">Command Palette</div>
        </div>
      )}
    </div>
  )
}

export default function Root() {
  return (
    <KeyProvider debug>
      <App />
    </KeyProvider>
  )
}
```

***

## Exercises

1. Create a `KeyProvider` and register three shortcuts with `useKeyCommands`.
2. Add a `useKeyBind` for <Kbd>Escape</Kbd> that only fires when a modal is open.
3. Try removing the `KeyProvider` and confirm that `useKeyBind` still works standalone.

}>
  **Next:** [Lesson 5  -  Multi-Key Sequences](/duck-vim/course/05-sequences)