2

In a VUE 3 component, I have set onBeforePrint and onAfterPrint thusly:

onMounted(() => {
  window.onbeforeprint = () => {
    clearPlaceholders();
  };
  window.onafterprint = () => {
    setPlaceholders();
  };
});

Do I need to unset these when the page unmounts and, if I do, what are best practices to unset these?

Norm Strassner
  • 225
  • 3
  • 11

1 Answers1

3

Listeners added on external elements 1 (body, window, document, ancestors, etc...) in onMounted hook should be removed in the onBeforeUnmount hook 2.

import { onMounted, onBeforeUnmount } from 'vue'
//...

  onMounted(() => {
    window.addEventListener('beforeprint', clearPlaceholders)
    window.addEventListener('afterprint', setPlaceholders)
  })

  onBeforeUnmount(() => {
    window.removeEventListener('beforeprint', clearPlaceholders)
    window.removeEventListener('afterprint', setPlaceholders)
  })

//...

Important: To remove a previously bound listener, the same event name and handler must be passed to removeEventListener as the ones passed to addEventListener.
In other words, separate arrow functions running the same handler are separate handlers, because:

const a = () => clearPlaceholders()
const b = () => clearPlaceholders()
console.log(a === b) // false

// So this wouldn't work:
target.addEventListener(eventName, a)
target.removeEventListener(eventName, b)  // ❌ fails

// the listener is removed by:
target.removeEventListener(eventName, a)  // ✅ succeeds

Read matching event listeners for removal for details.


1 - cleanup is not necessary on elements contained inside component's template as they are garbage collected when the DOM elements are removed.

2 - The listener cannot be removed in the onUnmounted hook if it (the listener) is defined inside the component because at the time the onUnmounted hook is fired component contents have been deleted (in fairness, onAfterUnmounted would have been more useful for understanding when this hook gets fired).


Reduce boilerplate (optional)

If you find yourself having to add/remove a lot of events on external targets, you could reduce the boilerplate by using a composable:

import { useEventListener } from './useEventListener'

  //...
  useEventListener(window, 'beforeprint', clearPlaceholders)
  useEventListener(window, 'afterprint', setPlaceholders)
  useEventListener(window, 'scroll', onScrollHandler, { passive: true })
  useEventListener(document.body, 'click', onBodyClickHandler, {
    capture: true
  })

And the composable - useEventListener.js - would look like this:

import { onMounted, onBeforeMount } from 'vue'

export const useEventListener = (target, ...args) => {
  onMounted(() => {
    target.addEventListener(...args)
  })
  onBeforeUnmount(() => {
    target.removeEventListener(...args)
  })
}

Note this composable could only be used in setup() (or <script setup>).

tao
  • 82,996
  • 16
  • 114
  • 150