An interesting fact on using removeEventListener() inside a web component
Tags: webcomponents, javascript, webdev, frontend
Published: 2023-07-27 03:38:55

Last few days I was working on this [`pods-tooltip`](https://github.com/heybran/pods-tooltip) web components hoping to replace existing tooltip inside [Pods Framework](https://github.com/pods-framework/pods) to have styles encapsulated inside shadowRoot, so it'd easier to update tooltip styles whenever it's needed without worrying about breaking styles on other UI elements.

Let's look at some codes.

```javascript
class PodsTooltip extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
   }

  connectedCallback() {
    // ...
  }

  closeTooltip() {
    this.tooltipContent.classList.remove("visible");
    this.removeEventListener('focusout', this.closeTooltip.bind(this));
  }

  openTooltip() {
    this.tooltipContent.classList.add("visible");
    this.addEventListener('focusout', this.closeTooltip.bind(this));
  }
}

customElements.define("pods-tooltip", PodsTooltip);
```

1. When tooltip is opened, I've added a `focusout` event and call `closeTooltip()` called with `bind(this)` as I want `this` to have the correct context inside `closeTooltip()` method.
2. Then then this event listener when tooltip is closed.

But the way I use `removeEventListener()` does not work as expected, it won't remove the event listener when testing.

In case you're wondering, this failed as well.

```javascript
class PodsTooltip extends HTMLElement {
  // ...

  closeTooltip() {
    // ...
    this.removeEventListener('focusout', this.closeTooltip);
  }

  openTooltip() {
    // ...
    this.addEventListener('focusout', this.closeTooltip.bind(this));
  }
}
```

According to [Function.prototype.bind()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) on MDN website.

> The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

So we need to store this new function in our component, or get rid of the `bind()` call. I think of 3 solutions for this problem.

## 1. Store this new function inside `constructor()`

```javascript
class PodsTooltip extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
    // Store the new function here
    this.closeTooltip = this.closeTooltip.bind(this);
   }

  connectedCallback() {
    // ...
  }

  closeTooltip() {
    // ...
    this.removeEventListener('focusout', this.closeTooltip);
  }

  openTooltip() {
    // ...
    this.addEventListener('focusout', this.closeTooltip);
  }
}
```

Now inside `removeEventListener()`, we're reference the correct function. If you print out `this.closeTooltip.name`, it's "bound closeTooltip".

```javascript
console.log(this.closeTooltip.name); // bound closeTooltip
```

But this solution doesn't feel intuitive as we might forget to store the new function inside `constructor`. Plus, we need to update function names in two places.

## 2. Use arrow function

```javascript
class PodsTooltip extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
   }

  connectedCallback() {
    // ...
  }

  /**
   * Change to arrow function here
   */
  closeTooltip = () => {
    // ...
    this.removeEventListener('focusout', this.closeTooltip);
  }

  openTooltip() {
    // ...
    this.addEventListener('focusout', this.closeTooltip);
  }
}
```

For arrow function inside `closeTooltip()`, `this` context will be component itself, so we no longer need to `bind()` the function when adding event listener.

## 3. Use static function

```javascript
class PodsTooltip extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
   }

  connectedCallback() {
    // ...
  }

  /**
   * Change to static function here
   */
  static closeTooltip() {
    // ...
    this.removeEventListener('focusout', PodsTooltip.closeTooltip);
  }

  openTooltip() {
    // ...
    this.addEventListener('focusout', PodsTooltip.closeTooltip);
  }
}
```

For static method, we can reference it by `PodsTooltip.closeTooltip`, and another possible way to reference it is like this.

```javascript
class PodsTooltip extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: "open" });
   }

  connectedCallback() {
    // ...
  }

  static closeTooltip() {
    // ...
    this.removeEventListener('focusout', this.constructor.closeTooltip);
  }

  openTooltip() {
    // ...
    this.addEventListener('focusout', this.constructor.closeTooltip);
  }
}
```