Using window.addEventListener with react hooks

April 11, 2019

A post by Will Mayger
Twitter . Instagram

React hooks can be a little confusing at first, especially when it comes to niche parts such as using window.addEventListener, or trying to achieve certain lifecycle events.

This is a quick post explaining best practices for using react hooks with addEventListener.

Generally speaking you should try to avoid adding event listener to the window object when using react and try to add them in more specifically for each node.

But, with this being said, you can’t always avoid it for example when using the addEventListener for resize. Which means you will then need to handle this properly using react hooks otherwise you will end up with hundreds of event listeners being added to the window object.

Using classes, and not react hooks, using addEventListener would have looked something like this:

export default class ReactHooksEventListenerDemo {
  state = {
      ...
  }

  componentDidMount() {
      window.addEventListener('resize', this.resizeEvent);
  }

  componentWillUnmount() {
      window.removeEventListener('resize', this.resizeEvent);
  }

  resizeEvent = (e) => {
      ...
  }
}

Here you would add the event listener in by calling window.addEventListener in react’s componentDidMount lifecycle method.

You would have needed to create a function that will then handle the event and so it is also visible within the other lifecycle methods, such as componentWillUnmount.

Finally you will need to remove the event listener when the component will unmount by calling window.removeEventListener in componentWillUnmount.

When using react hooks with addEventListener, we can get this down to ONE LINE OF CODE, that is reusable, maintainable and most importantly safer to use.

React hooks were introduced in react 16.8, and whilst it is not trying to replace using classes it does make it a lot cleaner and easier to use.

Here is a basic implementation and explanation of a react hook:

import React, { useState } from "react"

export default function BasicHook() {
  const [hit, setHit] = useState(0)

  return (
    <button onClick={() => setHit(hit + 1)}>You clicked {hit} time(s)!</button>
  )
}

Here we initialize the state variable to 0 using the following bit of code useState(0). On the same line we extract the values that are being returned which is very similar to using a getter and a setter which we are obtaining using array destructuring.

The values in the example will be like this:

  hit = state variable
  setHit = function to update state variable

Now we have the state object and the setter, we simply need to use them. In the jsx, a user will click the button causing the setter to add one to the current state variable value and then intern display it in the jsx.

Now, let’s start adding in our event listener logic into this.

We will do this by creating our own hook to handle it. It’s actually very simple to create your own react hook, it is literally just using the hooks the same way as we did before except saving them out in another file so that we can use it again and again.

To create our hook, let’s first call it useEvent because it will be replicating the window.addEventListener functionality.

It is best practice to name hooks with the word use followed by what it does, when using react hooks.

First, we should take a moment to think about how we will want to use this hook, before coding it, to make sure we are not writing unnecessary code.

If we look at the regular way of using this, it would look a little like window.addEventListener('event', handler, passive) and window.removeEventListener('event', handler) so as both functions share the same parameters we want this to use this hook in one line of code, and with three params.

So ideally we would want to use our function like this: useEvent('event', handler, passive).

Great, now let’s take a look at the code for our addEventListener react hook.

import { useEffect } from "react"

export default function useEvent(event, handler, passive = false) {
  useEffect(() => {
    // initiate the event handler
    window.addEventListener(event, handler, passive)

    // this will clean up the event every time the component is re-rendered
    return function cleanup() {
      window.removeEventListener(event, handler)
    }
  })
}

As you can see, the hook itself is pretty small, and simple.

We are using the useEffect hook provided from the react library because useEffect gets triggered each time react renders/re-renders the component.

So you can think of it a little bit like componentDidUpdate.

The body of useEffect, by itself, would mean that the event listener will be added on each render which would not be good, unless we were removing it each time as well.

And that is exactly what the useEffect function allows you to do, you can provide a cleanup function in the form of a kind of closure that will run at the appropriate time.

So, in the body of useEffect, we are adding in our window.addEventListener, and in the cleanup function we are adding in window.removeEventListener so that react knows how to remove it, meaning there will only ever be one instance of the event listener and will be removed when unmounted.

This provides us with a clean and safe implementation of window.addEventListener using react hooks.

If you want to copy the addEventListener react hook code from github, you can find it here: https://github.com/WillMayger/use-event

Otherwise you can find it as an npm module here: https://www.npmjs.com/package/use-add-event

I hope you have found this useful!

Interested in learning how to use componentDidMount in react hooks next? Using componentDidMount in react hooks

Will