Mixin for destroyed Vue component is still listening for events

Viewed 701

I have a parent component that conditionally renders one of two child components:

<template>
  <div>
    <!-- other code that changes conditional rendering -->

    <folders v-if="isSearchingInFolders" :key="1234"></folders>
    <snippets v-if="!isSearchingInFolders" :key="5678"></snippets>
  </div>
</template>

Each of these components use the same mixin (searchMixin) locally like so:

<template>
  <div>
    <div>
      <snippet
        v-for="item in items"
        :snippet="item"
        :key="item.id">
      </snippet>
      <img v-if="busy" src="/icons/loader-grey.svg" width="50">
    </div>
    <button @click="getItems">Get More</button>
  </div>
</template>

<script>
import searchMixin from './mixins/searchMixin';
import Snippet from './snippet';

export default {
  components: { Snippet },

  mixins: [searchMixin],

  data() {
    return {
      resourceName: 'snippets'
    }
  },
}
</script>

Each of the components is functionally equivalent with some slightly different markup, so for the purposes of this example Folders can be substituted with Snippets and vice versa.

The mixin I am using looks like this (simplified):

import axios from 'axios'
import  { EventBus } from '../event-bus';

export default {

  data() {
    return {
      hasMoreItems: true,
      busy: false,
      items: []
    }
  },

  created() {
    EventBus.$on('search', this.getItems)
    this.getItems();
  },

  destroyed() {
    this.$store.commit('resetSearchParams')
  },

  computed: {
    endpoint() {
      return `/${this.resourceName}/search`
    },

    busyOrMaximum() {
      return this.busy || !this.hasMoreItems;
    }
  },

  methods: {
    getItems(reset = false) {
      <!-- get the items and add them to this.items -->
    }
  }
}

In the parent component when I toggle the rendering by changing the isSearchingInFolders variable the expected component is destroyed and removed from the DOM (I have checked this by logging from the destroyed() lifecycle hook. However the searchMixin that was included in that component does not appear to be destroyed and still appears to listen for events. This means that when the EventBus.$on('search', this.getItems) line is triggered after changing which component is actively rendered from the parent, this.getItems() is triggered twice. Once for folders and once for snippets!

I was expecting the mixins for components to be destroyed along with the components themselves. Have I misunderstood how component destruction works?

1 Answers

Yes, when you pass an event handler as you do EventBus keeps the reference to the function you passed into. That prevents the destruction of the component object. So you need clear the reference by calling EventBus.$off so that the component can be destructed. So your destroy event hook should look like this:

destroyed() {
    this.$store.commit('resetSearchParams')
    EventBus.$off('search', this.getItems)
  },
Related