snippet
A useEventListener hook that doesn't go stale
Attaching an event listener in React without re-binding on every render or capturing a stale callback.
The trap with addEventListener in an effect is the dependency array: put the
handler in it and you re-bind on every render; leave it out and you capture a
stale closure. Storing the handler in a ref sidesteps both.
import { useEffect, useRef } from "react";
export function useEventListener<K extends keyof WindowEventMap>(
type: K,
handler: (event: WindowEventMap[K]) => void,
) {
const savedHandler = useRef(handler);
// Keep the ref current without re-running the listener effect.
useEffect(() => {
savedHandler.current = handler;
}, [handler]);
useEffect(() => {
const listener = (event: WindowEventMap[K]) => savedHandler.current(event);
window.addEventListener(type, listener);
return () => window.removeEventListener(type, listener);
}, [type]);
}The listener effect only depends on type, so it binds once. The handler always
reads through savedHandler.current, so it's never stale.
#react#hooks#typescript