@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