Skip to content

Instantly share code, notes, and snippets.

@slinkardbrandon
Last active March 4, 2021 18:45
Show Gist options
  • Save slinkardbrandon/2b3985a03c099f25cd2d83770a3d916a to your computer and use it in GitHub Desktop.
Save slinkardbrandon/2b3985a03c099f25cd2d83770a3d916a to your computer and use it in GitHub Desktop.
React useKeyBinds hook
export const MyComponent: React.FC = () => {
useKeyBinds({
"ctrl+z": () => {
console.log('undo');
},
"ctrl+y": () => {
console.log('redo');
},
});
return (
<div>
Some stuff you can edit, undo, and redo with!
</div>
);
};
import React, { useEffect, useMemo } from "react";
// prettier-ignore
type Alphabet = 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm' | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y'| 'z';
type Numbers = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9";
type SpecialChars = "enter" | "tab" | "escape" | "delete" | "backspace" | "tab";
type PrimaryKey = Alphabet | Numbers | SpecialChars;
type ModifierKey = "ctrl" | "alt" | "shift" | "meta";
type PartialRecord<K extends keyof any, T> = {
[P in K]?: T;
};
export type KeyBind = PrimaryKey | `${ModifierKey}+${PrimaryKey}`;
export type KeyBindValue = () => void;
export type KeyBinds = PartialRecord<KeyBind, KeyBindValue>;
export type Listener = (ev: KeyboardEvent) => void;
export function useKeyBinds(providedKeyBindMap: KeyBinds) {
const lowerBindMap = useMemo(() => {
return Object.keys(providedKeyBindMap).reduce((prev, curr) => {
prev[curr.toLowerCase()] = providedKeyBindMap[curr];
return prev;
}, {});
}, [providedKeyBindMap]);
const executeListenerCallback = React.useCallback(
(ev: KeyboardEvent, callback: () => void) => {
ev.stopPropagation();
callback();
},
[]
);
useEffect(() => {
const listener: Listener = (ev: KeyboardEvent) => {
if (event.target instanceof HTMLInputElement) {
return;
}
const key = ev.key.toLowerCase();
const ctrlVariant = `ctrl+${[key]}`;
const shiftVariant = `shift+${[key]}`;
const metaVariant = `meta+${[key]}`;
if (ev.shiftKey && lowerBindMap[shiftVariant]) {
return executeListenerCallback(ev, lowerBindMap[shiftVariant]);
}
if (ev.metaKey && lowerBindMap[metaVariant]) {
return executeListenerCallback(ev, lowerBindMap[metaVariant]);
}
if (ev.ctrlKey && lowerBindMap[ctrlVariant]) {
return executeListenerCallback(ev, lowerBindMap[ctrlVariant]);
}
if (lowerBindMap[key]) {
return executeListenerCallback(ev, lowerBindMap[key]);
}
};
document.body.addEventListener("keydown", listener);
return () => {
document.body.removeEventListener("keydown", listener);
};
}, [executeListenerCallback, lowerBindMap]);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment