HiveBrain v1.2.0
Get Started
← Back to all entries
snippetjavascriptTip

Listening for events with React hooks

Submitted by: @import:30-seconds-of-code··
0
Viewed 0 times
hookseventswithreactlisteningfor

Problem

React already provides a way to handle events on components. However, there may be occasions where you need to listen for events on the Window object or other elements. In such cases, you can create custom hooks to handle these events.
A simple useEventListener hook can be useful for adding event listeners to elements and cleaning them up when the component unmounts.
In order to implement it, you can use the useRef() hook to create a ref that will hold the handler, and the useEffect() hook to update the value of the savedHandler ref any time the handler changes. You can then add an event listener to the given element and clean up when unmounting.
A very common use-case for event listeners is to listen for key presses. The useKeyPress hook listens for changes in the pressed state of a given key.
Its implementation uses the useState() hook to create a state variable that holds the pressed state of the given key. It then defines two handler functions that update the state variable on key down or key up accordingly.

Solution

const useEventListener = (type, handler, el = window) => {
  const savedHandler = React.useRef();

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

  React.useEffect(() => {
    const listener = e => savedHandler.current(e);

    el.addEventListener(type, listener);

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

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

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

  useEventListener('mousemove', updateCoords);

  return (
    <p>Mouse coordinates: {coords.x}, {coords.y}</p>
  );
};

ReactDOM.createRoot(document.getElementById('root')).render(
  <MyApp />
);


In order to implement it, you can use the useRef() hook to create a ref that will hold the handler, and the useEffect() hook to update the value of the savedHandler ref any time the handler changes. You can then add an event listener to the given element and clean up when unmounting.
A very common use-case for event listeners is to listen for key presses. The useKeyPress hook listens for changes in the pressed state of a given key.
Its implementation uses the useState() hook to create a state variable that holds the pressed state of the given key. It then defines two handler functions that update the state variable on key down or key up accordingly.
Finally, using the useEffect() hook and EventTarget.addEventListener(), it handles the 'keydown' and 'keyup' events, and uses EventTarget.removeEventListener() to perform cleanup after the component is unmounted.
A variation of the useEventListener hook is the useHover hook, which listens for the 'mouseover' and 'mouseout' events on a given element. As these mouse events are common, this hook can be useful for handling hover states.
The implementation is similar to the useEventListener hook, but it uses two separate handlers for 'mouseover' and 'mouseout' events. It also uses a ref to keep track of the last node passed to the callbackRef function, allowing it to remove the listeners when the component unmounts.

Code Snippets

const useEventListener = (type, handler, el = window) => {
  const savedHandler = React.useRef();

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

  React.useEffect(() => {
    const listener = e => savedHandler.current(e);

    el.addEventListener(type, listener);

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

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

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

  useEventListener('mousemove', updateCoords);

  return (
    <p>Mouse coordinates: {coords.x}, {coords.y}</p>
  );
};

ReactDOM.createRoot(document.getElementById('root')).render(
  <MyApp />
);
const useKeyPress = targetKey => {
  const [keyPressed, setKeyPressed] = React.useState(false);

  const downHandler = ({ key }) => {
    if (key === targetKey) setKeyPressed(true);
  };

  const upHandler = ({ key }) => {
    if (key === targetKey) setKeyPressed(false);
  };

  React.useEffect(() => {
    window.addEventListener('keydown', downHandler);
    window.addEventListener('keyup', upHandler);

    return () => {
      window.removeEventListener('keydown', downHandler);
      window.removeEventListener('keyup', upHandler);
    };
  }, []);

  return keyPressed;
};

const MyApp = () => {
  const wPressed = useKeyPress('w');

  return <p>The "w" key is {!wPressed ? 'not ' : ''}pressed!</p>;
};

ReactDOM.createRoot(document.getElementById('root')).render(
  <MyApp />
);
const useHover = () => {
  const [isHovering, setIsHovering] = React.useState(false);

  const handleMouseOver = React.useCallback(() => setIsHovering(true), []);
  const handleMouseOut = React.useCallback(() => setIsHovering(false), []);

  const nodeRef = React.useRef();

  const callbackRef = React.useCallback(
    node => {
      if (nodeRef.current) {
        nodeRef.current.removeEventListener('mouseover', handleMouseOver);
        nodeRef.current.removeEventListener('mouseout', handleMouseOut);
      }

      nodeRef.current = node;

      if (nodeRef.current) {
        nodeRef.current.addEventListener('mouseover', handleMouseOver);
        nodeRef.current.addEventListener('mouseout', handleMouseOut);
      }
    },
    [handleMouseOver, handleMouseOut]
  );

  return [callbackRef, isHovering];
};

const MyApp = () => {
  const [hoverRef, isHovering] = useHover();

  return <div ref={hoverRef}>{isHovering ? 'Hovering' : 'Not hovering'}</div>;
};

ReactDOM.createRoot(document.getElementById('root')).render(
  <MyApp />
);

Context

From 30-seconds-of-code: event-listener-hooks

Revisions (0)

No revisions yet.