Search code examples
vue-componentvuejs3onbeforeprint

Vue 3 If onbeforeprint and onafterprint are set in onMounted, do I need to reset them in onBeforeUnmount (and how)?


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?


Solution

  • 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>).