Using event listeners in React hooks

Usually, when talking about React hooks, we think about some state management or handling side effects. Of course, that's a reasonable approach, but let's assume some cases when we should react for some event (can be a DOM event or a custom one).

We would start with adding some event listeners for the component, but then how about rerenders? The first idea is to wrap it with useEffect a hook and add a cleanup function. So let's do it now:

useEffect(() => {
  window.addEventListener(type, handler);
  return () => {
    window.removeEventListener(type, handler);
  }
}, [type]);

type and handler are variables that we should specify

So far so good, but still handler will be changed for each render. How we could keep it? Of course with ref. Let's create a custom hook then:

const useEventListener = ({ type, handler, element = window }) => {
  const savedHandler = useRef();

  useEffect(() => {
    savedHandler.current = handler
  }, [handler]);

  useEffect(() => {
    const listener = (event) => savedHandler.current(event);
    element.addEventListener(type, listener);

    return () => {
      element.removeEventListener(type, listener);
    }
  }, [type, element]);
}

As you can see, our hook looks pretty straightforward, it takes a couple of properties, a cache handler in ref and the rest of the code is the same. So from now on, we can use it in some components, for example, to track cursor position.

const App = () => {
  const [coords, setCoords] = useState({ x: 0, y: 0 });

  const updateCoords = useCallback((event) => {
     setCoords({ x: event.clientX, y: event.clientY });
  }, [setCoords]);

  useEventListener({ type: 'mousemove', handler: useCoords });

  return (
    <div>Your cursor position is {coords.x}, {coords.y}</div>
  );
}