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