How to Listen to Events in ReactJS
Tags: typescript, opensource, webdev, react
Published: 2022-03-24 01:48:32

## TL;DR

- [React Hook Solution](https://github.com/donavon/use-event-listener)
- [Original Discussion Thread](https://www.querythreads.com/listen-to-keypress-for-document-in-reactjs/)

## Contents

- [Introduction](#introduction)
- [How It Works](#how-it-works)
- [Conclusion](#conclusion)

## Introduction

&emsp; In my [last article](https://dev.to/tieje/i-wasted-two-days-jerry-rigging-a-package-when-the-solution-was-one-search-away-1ikp#conclusion), I created a static hexagonal grid with pan and zoom functionality. That's cool, but what's not cool is needing to press the one of the follow buttons to switch between *pointer mode* and *drag mode*:

![SVG app tools](https://i.ibb.co/TBFkGCZ/Screen-Shot-2022-03-23-at-9-43-31-PM.png)

&emsp; Instead of pressing these toolbar buttons, I'd like to use a keyboard shortcut to switch between the *pointer mode* and the *drag mode*. Copying Figma's button shortcuts, specifically, I would like to bind the `v` button and the `h` button keyboard keys to the *pointer mode* and *drag mode*, respectively. This functionality was achieved thanks to the [`use-event-listener`](https://github.com/donavon/use-event-listener) hook. Fortunately, the React hook is simple enough to use as brief case study into how event listeners work.

## How It Works

&emsp; The `useEventListener` hook is only one file with a brief amount of code:

```js
/* eslint-disable max-params */
import { useRef, useEffect } from 'react';

const useEventListener = (
  eventName,
  handler,
  element = global,
  options = {}
) => {
  const savedHandler = useRef();
  const { capture, passive, once } = options;

  useEffect(() => {
    savedHandler.current = handler;
  }, [handler]);

  useEffect(() => {
    const isSupported = element && element.addEventListener;
    if (!isSupported) {
      return;
    }

    const eventListener = (event) => savedHandler.current(event);
    const opts = { capture, passive, once };
    element.addEventListener(eventName, eventListener, opts);
    return () => {
      element.removeEventListener(eventName, eventListener, opts);
    };
  }, [eventName, element, capture, passive, once]);
};

export default useEventListener;
```

> <cite>[donavan's original code](https://github.com/donavon/use-event-listener/blob/develop/src/index.js)</cite>

&emsp; Let's break it down.

```js
const useEventListener = (
  eventName,
  handler,
  element = global,
  options = {}
) => {
  const savedHandler = useRef();
  const { capture, passive, once } = options;
```

&emsp; The `useEventListener` requires the event name and the handler function. In my case, the event I'm looking for is `keypress` and the function I made is `handlePanZoomModeSwitch`

```js
useEventListener('keypress', handlePanZoomModeSwitch)
```

&emsp; Because I'm simply checking if the keyboard `v` or `h` key is pressed, it's perfectly fine to use the default element `global`.

&emsp; There are three available options, despite [four possible options](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener). None of them fit my needs, however.

```js
  useEffect(() => {
    savedHandler.current = handler;
  }, [handler]);
```

&emsp; `savedhandler` is assigned to the `useRef()` [which is used to access DOM nodes and persist mutable values across re-renders](https://learnreact.design/posts/react-useref-by-example). In our, case we don't want to forget whatever state is already attached to the current DOM in our window. Since the second `useEffect()` parameter is specified as `[handler]`, whenever the handler function changes, the component will re-render itself. If the second `useEffect()` parameter was not specified, as in simply being `[]`, then the component will only render the component once.

&emsp; The last useEffect hook looks lengthy, but it's not that complex. The `isSupported` if-statement just checks if the element exists and if we can add an event listener to that element.

```js
  useEffect(() => {
    const isSupported = element && element.addEventListener;
    if (!isSupported) {
      return;
    }

    const eventListener = (event) => savedHandler.current(event);
    const opts = { capture, passive, once };
    element.addEventListener(eventName, eventListener, opts);
    return () => {
      element.removeEventListener(eventName, eventListener, opts);
    };
  }, [eventName, element, capture, passive, once]);
```

&emsp; Next, the `eventListener` arrow function serves as the handler function for the addEventListener. The `eventListener` function simply passes whatever event occurs to the handler function that we specified.

&emsp; [Finally, the `removeEventListener()` is passed to prevent memory leaks, side-effects, and event collisions.](https://stackoverflow.com/questions/53256662/react-why-should-i-remove-event-listeners)

## Conclusion

The `useEventListener()` hook makes it easy to bind keys for your web app needs. Have fun out there!