Files
skills/docs/ui/react/react-midi/src/provider/midi-context.md
2026-05-29 08:20:56 +01:00

2.5 KiB

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

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

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.