React: Event Listeners

In React, when you want to listen to an event, you can normally use one of the on- props. For example:

<input
  onChange={() => {
    console.log("Changed!");
  }}
/>

However, when you want to add an event listener to something else than a DOM element directly contained within the component, for example to the window or to the document, you can use the addEventListener method. An example of adding such an event listeners to a component is shown below.

import { useEffect, useState } from "react";

export default function App() {
  const [isListening, setIsListening] = useState(true);
  const [lastPressedKey, setLastPressedKey] = useState(null);

  useEffect(() => {
    /**
     * Only if listening is true, we will listen for key presses.
     *
     * This is for demonstration purposes only, of course you may remove this
     * if-statement if you always want to have an event listener while the
     * component is mounted.
     */
    if (isListening) {
      /**
       * The callback function that will be invoked when a key is pressed.
       *
       * Note that we DO NOT declare the callback function inline e.g.
       *
       * document.addEventListener("keydown", () => {}); // WRONG
       *
       * If we would declare it inline in both the `addEventListener` and
       * `removeEventListener` calls, both functions would refer to a different
       * place in memory, even if they had the same content; making it
       * impossible to determine which listener should be removed.
       *
       * Instead, we declare it as a separate "listener" function. Then, the
       * added listener callback can be matched to the one to be removed.
       */
      function listener(e) {
        setLastPressedKey(e.key);
      }

      // The listener is added.
      document.addEventListener("keydown", listener);

      /**
       * We return a new function that will be called in 2 conditions:
       *
       * - when the component is unmounted
       * - when the useEffect is re-run (when a value in the dependency array
       *   changes)
       *
       * Note that the return is being done inside the if-statement, so the
       * function will only be returned if the if-statement is true (i.e. the
       * listener should only be cleaned up if it is added in the first place).
       */
      return () => {
        // The listener is removed.
        document.removeEventListener("keydown", listener);
      };
    }
    /**
     * The useEffect will run again if the value of the dependency array
     * changes.
     */
  }, [isListening]);

  return (
    <div>
      <h1>React Event Listeners</h1>
      <p>Focus this window and type something</p>
      <div>Last Pressed Key: {lastPressedKey}</div>
      <button
        onClick={() => {
          setIsListening(!isListening);
        }}
      >
        {isListening ? "Stop Listening" : "Start Listening"}
      </button>
    </div>
  );
}

Below a simplified version with only the useEffect hook.

useEffect(() => {
  /**
   * The callback function that will be invoked when a key is pressed.
   *
   * Note that we DO NOT declare the callback function inline e.g.
   *
   * document.addEventListener("keydown", () => {}); // WRONG
   *
   * If we would declare it inline in both the `addEventListener` and
   * `removeEventListener` calls, both functions would refer to a different
   * place in memory, even if they had the same content; making it
   * impossible to determine which listener should be removed.
   *
   * Instead, we declare it as a separate "listener" function. Then, the
   * added listener callback can be matched to the one to be removed.
   */
  function listener(e) {
    console.log(e.key);
  }

  // The listener is added.
  document.addEventListener("keydown", listener);

  /**
   * We return a new function that will be called on 2 occasions:
   *
   * - when the component is unmounted
   * - when the useEffect is re-run (when a value in the dependency array
   *   changes)
   */
  return () => {
    // The listener is removed.
    document.removeEventListener("keydown", listener);
  };
}, []);
Splitting a folder into subfoldersUsing SoapUI in a Gitlab pipeline