@react-hookz/web
Version:
React hooks done right, for browser and SSR.
51 lines (50 loc) • 2.16 kB
JavaScript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useMemo } from 'react';
import { useIsMounted, useSyncedRef } from '..';
import { hasOwnProperty } from "../util/misc.js";
/**
* Subscribes an event listener to the target, and automatically unsubscribes
* it on unmount.
*
* @param target Element ref object or element itself.
* @param params Parameters specific for target's `addEventListener`. Commonly,
* it is `[eventName, listener, options]`.
*/
export function useEventListener(target, ...params) {
const isMounted = useIsMounted();
// create static event listener
const listenerRef = useSyncedRef(params[1]);
const eventListener = useMemo(() =>
// as some event listeners designed to be used through `this`
// it is better to make listener a conventional function as it
// infers call context
// eslint-disable-next-line func-names
function (...args) {
// normally, such situation should not happen, but better to
// have back covered
/* istanbul ignore next */
if (!isMounted())
return;
// we dont care if non-listener provided, simply dont do anything
/* istanbul ignore else */
if (typeof listenerRef.current === 'function') {
listenerRef.current.apply(this, args);
}
else if (typeof listenerRef.current.handleEvent === 'function') {
listenerRef.current.handleEvent.apply(this, args);
}
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[]);
useEffect(() => {
const tgt = target && hasOwnProperty(target, 'current') ? target.current : target;
if (!tgt)
return;
const restParams = params.slice(2);
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
tgt.addEventListener(params[0], eventListener, ...restParams);
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
return () => tgt.removeEventListener(params[0], eventListener, ...restParams);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [target, params[0]]);
}