A React hook for performantly attaching event listeners to the document

This hook lets you attach one or more event listeners to the document, while avoiding re-binding cycles that are a natural outcome of attaching handlers inside a useEffect.

const useDocumentEvent = (events, handler) => {
    const { get: getHandler } = useValue(handler);
    const _handler = useCallback(e => get()(e), [get]);
    useEffect(() => {
        const eventsArray = events.split(' ');
        eventsArray.forEach(event => document.addEventListener(event, _handler));
        return () => eventsArray.forEach(event => document.removeEventListener(event, _handler));
    }, [events, _handler]);

Usage examples:

// Attach an event listener to the document
useDocumentEvent('resize', e => {/* Do something */});

// Attach multiple event listeners to the document
useDocumentEvent('resize scroll', e => {/* Do something */});

Re-binding cycles in useEffect

In React, attaching event listeners is done via useEffect because:

  • You don't want to attach a new listener every render
  • You need to cleanup (remove the event listener) when your component unmounts

However, if your handler is depending on external values, you need to add those to the useEffect dependency list, which will cause it to remove the old listener and add the new listener every time that dependency changes. I refer to this phenomenon as "re-binding cycles".

This hook lets you avoid that.