Skip to main content

sometechblog.com

Manage Key Binding Across React Components in a Larger App

Table of Contents

When I started building my application I used a routing-based structured and as the application evolved I transitioned – at least partially – to a domain-driven structure. In the domain-driven structure the application is essentially divided in multiple smaller applications with their own domains. In my case that includes “generators”, “dataSources”, “subscriptions”.

This means that everything related to the subscription domain is located in the src/modules/subscriptions folder and then consumed on various pages be it pricing page or as a modal popup on the app page.

One challenge with this structure is when you have functionality “overlapping” domains. An example of this is shortcuts using key bindings. On the app page there are multiple shortcuts from different domains that are located separately. Below is the app page with components and their domains:

Different modules utilized on the app page

To avoid shortcut clashes and be able to track and manage shortcut, I lifted up the functionality into a cross domain folder, src/lib/. A simple hook should suffice.

# Key Binding Hook

First of all I need a versatile hook that can be called with a sequence of keys and a callback function for when the sequence of keys have been pressed. I created the src/lib/useKeyBinding.ts file and the useKeyBinding hook:

import { useCallback, useEffect } from 'react';

type Props = {
  keyBinding: string[];
  callback: (event: KeyboardEvent) => void;
};

// Versatile hook for catching shortcuts and invoke a callback
const useKeyBinding = ({ keyBinding, callback }: Props) => {
  const handleCallback = useCallback(
    (event: KeyboardEvent) => {

      // Check if all keys where clicked, ie. meta keys like shift can be clicked together with "K".
      const isSequenceClicked = keyBinding.every((key) => {
        const keyLowerCase = key.toLowerCase();

        if (keyLowerCase === 'meta') {
          return event.metaKey;
        }

        if (keyLowerCase === 'shift') {
          return event.shiftKey;
        }

        if (keyLowerCase === 'ctrl') {
          return event.ctrlKey;
        }

        if (keyLowerCase === 'alt') {
          return event.altKey;
        }

        return keyLowerCase === event.key.toLowerCase();
      });

      if (!isSequenceClicked) {
        return;
      }

      callback(event);
    },
    [callback, keyBinding],
  );

  useEffect(() => {
    globalThis.window.addEventListener('keydown', handleCallback);
    return () => {
      globalThis.window.removeEventListener('keydown', handleCallback);
    };
  }, [handleCallback]);
};

Pass an array of shortcut keys, e.g. [“meta”, “k”], to the hook and it will invoke the callback function when the sequence of keys has been called. Using this hook I can remove much of the existing repetitive code I currently use for shortcuts. But I don’t have a way to see which shortcuts are used on the entire app page. To do this I created an additional hook useKeyBindingApp that is used exclusively for the app page and uses types to track active shortcuts:

//
// Key bindings for /app page
//
type AppProps = {
  keyBinding: // Submit generator
  | ['meta', 'enter']
    | ['ctrl', 'enter']
    // Generated content
    | ['shift', 'meta', 'f']
    | ['shift', 'ctrl', 'f']
    | ['shift', 'meta', 'e']
    | ['shift', 'ctrl', 'e']
    | ['shift', 'meta', 'v']
    | ['shift', 'ctrl', 'v']
    // Data Source CRUD View Search
    | ['meta', 'k']
    | ['ctrl', 'k']
    | ['meta', 'g']
    | ['ctrl', 'g']
    | ['arrowDown']
    | ['arrowUp']
    | ['escape'];
  callback: Props['callback'];
};

// Hook used on the app page to manage shortcuts
export const useKeyBindingApp = (props: AppProps) => useKeyBinding(props);

I can use the useKeyBindingApp in the generator form submit to handle AI generation requests:

// Code omitted at various places for brevity...
export const PromptSubmit = () => {
  const handleClick = () => dispatch(generatorStream());
  
  // Typescript will catch false key bindings
  useKeyBindingApp({ keyBinding: ['meta', 'enter'], callback: handleClick });
  useKeyBindingApp({ keyBinding: ['ctrl', 'enter'], callback: handleClick });

  return (
    <Button size="sm" onClick={handleClick}>
      Generate
    </Button>
  );
};

The same hook is also used to invoke a shortcut to format generated SQL code using ["shift", "meta", "f"] in another component. Now I have a way to easily track and see which shortcuts are being using on the app page without having to dig through multiple folders and sub folders.