DEV Community

Matteo Santoro
Matteo Santoro

Posted on

7 2 2

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

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more