DEV Community

Matteo Santoro
Matteo Santoro

Posted on

How I Discovered to Not Put EventListeners in On Mount in Vue SPAs

Introduction

During a project for a client, I was tasked with integrating an event listener within a Vue-based Single Page Application (SPA), specifically for a component named "HomeView" that contained an iframe. The event, dubbed "triggerEvent," was initially set up during the 'onMount' lifecycle hook of HomeView. This setup led to unexpected behavior where navigating away and then returning to HomeView caused the "triggerEvent" to fire multiple times, indicating a stacking of event listeners.

Investigating the Issue

To understand the issue better, I experimented with location.replace() instead of router.push() for navigation. The former seemed to resolve the issue, suggesting that the method of navigation affected how event listeners behaved. Further tests with router.push() confirmed that each navigation added a new listener without removing the previous one, thereby multiplying the triggers.

The Root Cause

This behavior is rooted in how SPA frameworks like Vue manage event listeners. In Vue, components mounted with onMount do not automatically clean up their event listeners upon unmounting unless explicitly coded to do so. This leads to duplicated listeners if a component mounts multiple times, as is common in SPAs where router.push() is used for navigation without full page reloads.

Solutions

Cleanup on Component Unmount

To prevent this issue, one effective strategy is to remove event listeners when the component unmounts:

import { onMounted, onUnmounted } from 'vue';

export default {
  setup() {
    const handleEvent = () => console.log('Event triggered');
    onMounted(() => window.addEventListener('triggerEvent', handleEvent));
    onUnmounted(() => window.removeEventListener('triggerEvent', handleEvent));
  }
};
Enter fullscreen mode Exit fullscreen mode

Conditional Event Listener Attachment

Alternatively, checking if an event listener is already in place before adding a new one can prevent duplicates:

import { onMounted, ref } from 'vue';

const eventBound = ref(false);

export default {
  setup() {
    const handleEvent = () => console.log('Event triggered');
    onMounted(() => {
      if (!eventBound.value) {
        window.addEventListener('triggerEvent', handleEvent);
        eventBound.value = true;
      }
    });
  }
};
Enter fullscreen mode Exit fullscreen mode

Global Event Listener in App.vue

Another approach is to place the event listener in App.vue, which is only mounted once, thereby avoiding any reattachment issues:

import { onMounted } from 'vue';

export default {
  setup() {
    const handleEvent = () => console.log('Global event triggered');
    onMounted(() => window.addEventListener('triggerEvent', handleEvent));
  }
};
Enter fullscreen mode Exit fullscreen mode

Top comments (0)