first commit
This commit is contained in:
70
docs/ui/react/react-midi/src/provider/midi-context.md
Normal file
70
docs/ui/react/react-midi/src/provider/midi-context.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# `provider/midi-context`
|
||||
|
||||
## Purpose
|
||||
|
||||
Defines the two React contexts that wire `<MidiProvider>` to its
|
||||
descendant hooks. `MidiReactContext` carries the resolved
|
||||
`MidiContextValue` — status, `WebMIDIAccess`, any caught error, the
|
||||
`sysex` opt-in flag, and a monotonically-incrementing `stateVersion`
|
||||
counter bumped on each Web MIDI `statechange` event. The presence of
|
||||
`stateVersion` lets `useSyncExternalStore`-based hooks observe live
|
||||
device plug/unplug changes without each hook holding its own
|
||||
`statechange` listener.
|
||||
|
||||
`MidiRequestContext` is a per-tree override slot for the
|
||||
`navigator.requestMIDIAccess` function. Tests provide a deterministic
|
||||
in-memory implementation here; production renders leave it unset and
|
||||
the provider falls back to the real global. This is a real injection
|
||||
seam — not a mock — so the integration path remains exercised
|
||||
verbatim under both real and fake transports.
|
||||
|
||||
## Features
|
||||
|
||||
- Two distinct contexts so hooks that only need the resolved access
|
||||
re-render only on `stateVersion` bumps, not on injection changes.
|
||||
- `null` sentinel on `MidiReactContext` triggers
|
||||
`REACT_MIDI.NO_PROVIDER` in every public hook.
|
||||
- `MidiStatus` is the four-value lifecycle: `loading`, `granted`,
|
||||
`denied`, `unsupported`.
|
||||
|
||||
## Usage: Basic
|
||||
|
||||
```tsx
|
||||
import { MidiReactContext } from "@suaveplan/react-midi";
|
||||
import { useContext } from "react";
|
||||
|
||||
function StatusBadge() {
|
||||
const ctx = useContext(MidiReactContext);
|
||||
return <span>{ctx?.status ?? "no-provider"}</span>;
|
||||
}
|
||||
```
|
||||
|
||||
## Usage: Advanced
|
||||
|
||||
```tsx
|
||||
import { MidiProvider, MidiRequestContext } from "@suaveplan/react-midi";
|
||||
|
||||
// Inject a deterministic fake in tests:
|
||||
function fakeRequest() {
|
||||
return Promise.resolve(fakeAccessShape);
|
||||
}
|
||||
|
||||
<MidiRequestContext.Provider value={fakeRequest as never}>
|
||||
<MidiProvider sysex>{children}</MidiProvider>
|
||||
</MidiRequestContext.Provider>;
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `MidiStatus = "loading" | "granted" | "denied" | "unsupported"`.
|
||||
- `MidiContextValue { status, access, error, sysex, stateVersion }`.
|
||||
- `MidiReactContext: React.Context<MidiContextValue | null>`.
|
||||
- `MidiRequestContext: React.Context<WebMIDIRequestFn | null>`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`stateVersion` exists because the Web MIDI `statechange` event fires
|
||||
mutations on `MIDIAccess.inputs` / `outputs` in place — the underlying
|
||||
`Map` identity does not change. `useSyncExternalStore` therefore needs
|
||||
a sentinel value that *does* change to trigger a re-render; the
|
||||
provider increments `stateVersion` inside the `statechange` callback.
|
||||
Reference in New Issue
Block a user