How to (really) remove eventListeners in React
Tags: javascript, react, hooks, frontend
Published: 2020-03-27 06:38:40

Sometimes you need to track user interaction like e.g. scrolling or events like the change of the window size. In this cases you will add an `eventListener` to your `window`/`document`/`body` or whatever.

When working with eventListeners you always have to take care about cleaning them up, if the component doesn't need them anymore or gets unmounted.

## Mount & Unmount

A common and simple use case is to add a listener after the initial mount and remove it when the component unmounts. This can be done with the [useEffect](https://reactjs.org/docs/hooks-effect.html) hook.

Example:

```javascript
  const onKeyDown = (event) => { console.log(event) }

  useEffect(() => {
    window.addEventListener('keydown', onKeyDown)
  
    return () => { window.removeEventListener('keydown', onKeyDown) }
  }, [])

```

❗️Don't forget the second parameter `[]` when calling `useEffect`. Otherwise it will run on every render.

## State change or property change

What work's perfect in the example above, won't work when you add and remove listeners depending on a state or prop change (as i had to learn).

Example:

```javascript
  // ⚠️ This will not work!
  const [isVisible, setVisibility] = useState(false)

  const onKeyDown = (event) => { console.log(event) }

  handleToggle((isVisible) => {
    if (isVisible) window.addEventListener('keydown', onKeyDown)
    else window.removeEventListener('keydown', onKeyDown)
  })

  return (
    <button onClick={() => setVisibility(!isVisible)}>Click me!</button>
  )
```

After clicking the button the second time the eventListner should be removed. But that's not what will happen.

**But why?**

The `removeEventListener(event, callback)` function will internally do an equality check between the given callback and the callback which was passed to `addEventListener()`. If this check doesn't return true no listener will be removed from the window.

But we pass in the exact same function to `addEventListener()` and `removeEventListener()`! 🤯

Well,... not really.
As React renders the component new on every state change, it also assigns the function `onKeyDown()` new within each render. And that's why the equality check won't succeed.

### Solution

React provides a nice Hook called [useCallback()](https://reactjs.org/docs/hooks-reference.html#usecallback). This allows us to memoize a function and the equality check will succeed.

Example

```javascript
  const [isVisible, setVisibility] = useState(false)

  const onKeyDown = useCallback((event) => { console.log(event) }, [])

  handleToggle((isVisible) => {
    if (isVisible) window.addEventListener('keydown', onKeyDown)
    else window.removeEventListener('keydown', onKeyDown)
  })

  return (
    <button onClick={() => setVisibility(!isVisible)}>Click me!</button>
  )
```

❗️Again: Don't forget the second parameter `[]` when calling `useCallback()`. You can pass in an Array of dependencies here, to control when the callback should change. But that's not what we need in our case.

—


If you got any kind of feedback, suggestions or ideas - feel free to comment this blog post!