# `hooks/use-midi-input`
_(no submodules)_
## Purpose
`useMidiInput(selector)` returns a typed `MidiPortLike` adapter over
a single Web MIDI input. The adapter parses raw bytes into the
discriminated `MidiMessage` union before delivering them through
`onMessage`, so consumers never touch byte-level details. The
`selector` is either an explicit `MIDIInput.id` or the literal
`"first"` shorthand that picks whichever input the access enumerates
first — useful when the host has exactly one device plugged in.
The hook returns `null` when access is still pending, when the
selector misses (e.g. a stale id), or when there are zero inputs.
This null-return shape composes cleanly with conditional rendering;
consumers don't need to thread an explicit loading state.
## Features
- Two selector forms: `id` string or `"first"` literal.
- Returns `null` during loading / denial / unknown id.
- Adapter identity memoised against access + stateVersion + selector.
- Multi-subscriber fan-out from a single Web MIDI input.
## Usage: Basic
```tsx
import { useMidiInput } from "@suaveplan/react-midi";
import { useEffect } from "react";
function NoteListener() {
const port = useMidiInput("first");
useEffect(() => {
if (port === null) return;
return port.onMessage((msg) => {
if (msg.type === "noteOn") console.log(msg.note, msg.velocity);
});
}, [port]);
return null;
}
```
## Usage: Advanced
```tsx
function PerDeviceListeners() {
const { inputs } = useMidi();
return (
<>
{inputs.map((i) => (
))}
>
);
}
function PortFan({ portId }: { portId: string }) {
const port = useMidiInput(portId);
// ... attach handlers via port.onMessage
return null;
}
```
## API Reference
- `useMidiInput(selector: string | "first"): MidiPortLike | null`
## Implementation Notes
The hook calls `adaptInput` inside `useMemo` so the adapter identity
matches the underlying `MIDIInput` for the duration of the hook's
lifetime. Because `adaptInput` keeps its own `Set` and
the `useMemo` recreates the adapter when the underlying port changes
(plug/unplug), any active subscription should re-bind from a
`useEffect` keyed on `[port]`.