Slim Down Your React Code with the useEventListener Hook
Learn how the useEventListener hook in React can help reduce code complexity while keeping your app's event handling clean and readable.
Among all the existing React custom hooks I've been working on in the past, the one I like the most is probably useEventListener.
This React hook is probably less famous than other custom React abstractions such as useLocalStorage or useToggle, yet it is a great example of how composition and re-usability in React matter.
Why do we need a custom hook for event listeners?
Event handling is a core concept for any Web App.
Do you know those beautiful search bar that opens on a combination of keyboard buttons? Or those progress bar that grows on top of your blog post once you scroll and read the content?
All of them, and many more features, are implemented in setting event listeners in your app. Without them, we could still create wonderful Web apps, although we might missing some magical features that help create engagement.
Setting an event listener in a React component
Let's say we have a blog post <ProgressBar />
component and we want to make it fill the page width as the user scrolls the page over the content. For this example, we need to listen for the scroll
event.
To set this event listener, the component would probably look something like this:
Although a simple example, there are some details to look at and keep in mind:
- We need to create a side effect to set an event listener once. In this case, we don't have any dependency, so it will be created only on component mount.
- Once the listener is set, we should also handle the component unmount scenario. To prevent memory leaks in our app from forgotten ongoing event listeners, be sure to remove it using
.removeEventListener()
on the target element.
This feels short enough to say we don't need a custom hook for this. And you are right, there is no need to overengineer a project with tons of custom hooks.
However, if you work on a highly interactive app, you might need more event listeners even in the same component, and this can make your code less readable and maintainable in the long term.
Just imagine the app needs to react to 5–10 different key combinations, it can easily make the component code grow a lot.
useEventListener makes React event listeners easy
Let's take a look at my useEventListener
implementation from the @mountain-ui/react-hooks library.
This version supports multiple events in a single call in case the handler is the same, so we can keep its usage even cleaner in our components.
If we examine the implementation:
getElement
helps to resolve the target element or a given ref argument.eventList
,serializedEventList
andserializedOptions
are derived from the arguments to prepare the side effects definition.savedHandler
and the firstuseEffect
call are used to allow our event listener side effect to always get the latest handler. This is a pattern known as Latest Ref pattern.- The final side effect creates the listener and adds/removes it in the lifecycle of this hook, regenerating it once some options or events in the list change.
As said, this wouldn't change the previous example's complexity, but if we look at an example where a component should react to many events, we'll notice the real benefits.
This is already much shorter than how it would be without this abstraction.
If we go the extra mile, assume we also created a useKeyPress
custom hook on top of useEventListener
:
Wrapping up
useEventListener
it's great because it opens the door to many more custom hooks specialized in specific actions such as useKeyPress
, useScroll
, useClickOutside
and many more, I hope you'll enjoy refactoring some legacy code where this snippet might help you! Cheers 🎉
Last updated: