TL;DR
Contents
Introduction
In my last article, 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:
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
hook. Fortunately, the React hook is simple enough to use as brief case study into how event listeners work.
How It Works
The useEventListener
hook is only one file with a brief amount of code:
/* 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;
Let's break it down.
const useEventListener = (
eventName,
handler,
element = global,
options = {}
) => {
const savedHandler = useRef();
const { capture, passive, once } = options;
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
useEventListener('keypress', handlePanZoomModeSwitch)
Because I'm simply checking if the keyboard v
or h
key is pressed, it's perfectly fine to use the default element global
.
There are three available options, despite four possible options. None of them fit my needs, however.
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
savedhandler
is assigned to the useRef()
which is used to access DOM nodes and persist mutable values across re-renders. 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.
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.
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]);
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.
Conclusion
The useEventListener()
hook makes it easy to bind keys for your web app needs. Have fun out there!
Top comments (0)