How do I window removeEventListener using React useEffect

You can put the handleKeyUp function inside of the function given to useEffect (which is the recommended way of doing it according to the official documentation) and only add the listener and return a cleanup function when collapsed is false.

useEffect(() => {
  if (collapsed) {
    return;
  }

  function handleKeyUp(event) {
    switch (event.key) {
      case "Escape":
        setCollapsed(true);
        break;
    }
  }

  window.addEventListener("keyup", handleKeyUp);
  return () => window.removeEventListener("keyup", handleKeyUp);
}, [collapsed]);

Tholle's answer may work, but it's bad practice to declare a function inside an if.

It makes it harder to follow when the function is declared and when it is not. Also it can lead to bugs because functions are hoisted up.

There's a neater way to fix it:

By wrapping your event handler with the useCallback hook.

const [collapsed, setCollapsed] = useState(true)

const handleKeyUp = useCallback((event) => {
    if (event.key === "Escape") {
      setCollapsed(true)
    }
}, [setCollapsed])

useEffect(() => {
    if (!collapsed) {
        window.addEventListener("keyup", handleKeyUp)
    } else {
        window.removeEventListener("keyup", handleKeyUp)
    }

    return () => window.removeEventListener("keyup", handleKeyUp)
}, [collapsed, handleKeyUp])
  • useCallback has a dependency on setCollapsed. This makes sure handleKeyUp is not redefined when the component rerenders (which always happens when state changes)
  • useEffect will conditionally add/remove the event listener, otherwise events will keep firing as long as the component is mounted.

If you use a lot of event handlers in useEffect, there's a custom hook for that: https://usehooks.com/useEventListener/

Here's the question posters example updated with my solution: https://codepen.io/publicJorn/pen/eYzwENN