@fcannizzaro/streamdeck-react

Event Hooks

React hooks for Stream Deck hardware events -- key presses, dial rotation, touch taps, and dial hints.

Event hooks subscribe to Stream Deck hardware events and fire callbacks inside the React tree.

useKeyDown

Fires when a key is pressed down.

function useKeyDown(callback: (payload: KeyDownPayload) => void): void;
interface KeyDownPayload {
  settings: JsonObject;
  isInMultiAction: boolean;
  state?: number;
  userDesiredState?: number;
}
function CounterKey() {
  const [count, setCount] = useState(0);

  useKeyDown(() => {
    setCount((c) => c + 1);
  });

  return (
    <div
      style={{
        width: "100%",
        height: "100%",
        alignItems: "center",
        justifyContent: "center",
        background: "#000",
      }}
    >
      <span style={{ color: "white", fontSize: 32 }}>{count}</span>
    </div>
  );
}

useKeyUp

Fires when a key is released.

function useKeyUp(callback: (payload: KeyUpPayload) => void): void;
interface KeyUpPayload {
  settings: JsonObject;
  isInMultiAction: boolean;
  state?: number;
  userDesiredState?: number;
}
function HoldKey() {
  const [pressed, setPressed] = useState(false);

  useKeyDown(() => setPressed(true));
  useKeyUp(() => setPressed(false));

  return (
    <div
      style={{
        width: "100%",
        height: "100%",
        alignItems: "center",
        justifyContent: "center",
        background: pressed ? "#4CAF50" : "#333",
      }}
    >
      <span style={{ color: "white", fontSize: 18 }}>{pressed ? "HELD" : "PRESS"}</span>
    </div>
  );
}

useDialRotate

Fires when a dial/encoder is rotated.

function useDialRotate(callback: (payload: DialRotatePayload) => void): void;
interface DialRotatePayload {
  ticks: number; // Positive = clockwise, negative = counter-clockwise
  pressed: boolean; // Whether the dial is pressed while rotating
  settings: JsonObject;
}
function VolumeDial() {
  const [volume, setVolume] = useState(50);

  useDialRotate(({ ticks }) => {
    setVolume((v) => Math.max(0, Math.min(100, v + ticks)));
  });

  return (
    <div
      style={{
        width: "100%",
        height: "100%",
        alignItems: "center",
        justifyContent: "center",
        background: "#1a1a1a",
      }}
    >
      <span style={{ color: "white", fontSize: 28 }}>{volume}%</span>
    </div>
  );
}

useDialDown / useDialUp

Fire when a dial is pressed / released.

function useDialDown(callback: (payload: DialPressPayload) => void): void;
function useDialUp(callback: (payload: DialPressPayload) => void): void;
interface DialPressPayload {
  settings: JsonObject;
  controller: "Encoder";
}

useTouchTap

Fires when the touch strip area is tapped.

function useTouchTap(callback: (payload: TouchTapPayload) => void): void;
interface TouchTapPayload {
  tapPos: [x: number, y: number]; // Coordinates of the tap
  hold: boolean; // Whether it was a long press
  settings: JsonObject;
}

useDialHint

Sets the trigger descriptions shown on Stream Deck+ for what the dial actions do. Updates automatically when hints change.

function useDialHint(hints: DialHints): void;
interface DialHints {
  rotate?: string;
  press?: string;
  touch?: string;
  longTouch?: string;
}
function VolumeDial() {
  const [muted, setMuted] = useState(false);

  useDialHint({
    rotate: "Adjust volume",
    press: muted ? "Unmute" : "Mute",
  });

  // ...
}

Under the hood, useDialHint calls action.setTriggerDescription() whenever the hint values change.

Implementation Pattern

All event hooks follow the same internal pattern: they subscribe to the root's EventBus via useEffect and use a stable callback ref to avoid stale closures without causing re-subscriptions.

On this page