G.Abitbol
G.Abitbol

Reputation: 13

vue.js $emit not received by parent when multiple calls in short amount of time

I tried to implement a simple notification system with notification Store inspired by this snippet form Linus Borg : https://jsfiddle.net/Linusborg/wnb6tdg8/

It is working fine when you add one notification at a time, but when you add a second notification before the first one disappears the notificationMessage emit its "close-notification" event but the parent notificationBox component does not execute the "removeNotification" function. removeNotification is called after the emit if you use the click event on the notification though. So there is probably a issue with the timeout but i can't figure out what.

NotificationStore.js

class NotificationStore {
  constructor () {
    this.state = {
      notifications: []
    }
  }
  addNotification (notification) {    
    this.state.notifications.push(notification)
  }
  removeNotification (notification) {
    console.log('remove from store')
    this.state.notifications.splice(this.state.notifications.indexOf(notification), 1)
  }
}

export default new NotificationStore()

App.vue

<template>
  <div id="app">
    <notification-box></notification-box>
    <div @click="createNotif">
      create new notification
    </div>
  </div>
</template>

<script>
  import notificationMessage from './components/notificationMessage.vue'
  import notificationBox from './components/notificationBox.vue'
  import NotificationStore from './notificationStore'

  export default {
    name: 'app',
    methods: {
      createNotif () {
        NotificationStore.addNotification({
          name: 'test',
          message: 'this is a test notification',
          type: 'warning'
        })
      }
    },
    components: {
      notificationMessage,
      notificationBox
    }
  }
</script>

notificationBox.vue

<template>
  <div :class="'notification-box'">
      <notification-message v-for="(notification, index) in notifications" :notification="notification" :key="index" v-on:closeNotification="removeNotification"></notification-message>
  </div>
</template>

<script>
  import notificationMessage from './notificationMessage.vue'
  import NotificationStore from '../notificationStore'

  export default {
    name: 'notificationBox',
    data () {
      return {
        notifications: NotificationStore.state.notifications    
      }
    },
    methods: {
      removeNotification: function (notification) {
        console.log('removeNotification')
        NotificationStore.removeNotification(notification)
      }
    },
    components: {
      notificationMessage
    }
  }
</script>

notificationMessage.vue

<template>
    <div :class="'notification-message ' + notification.type" @click="triggerClose(notification)">
      {{ notification.message }}
    </div>
</template>

<script>
  export default {
    name: 'notificationMessage',
    props: {
      notification: {
        type: Object,
        required: true
      },
      delay: {
        type: Number,
        required: false,
        default () {
          return 3000
        }
      }
    },
    data () {
      return {
        notificationTimer: null
      }
    },    
    methods: {
      triggerClose (notification) {
        console.log('triggerClose')
        clearTimeout(this.notificationTimer)
        this.$emit('closeNotification', notification)
      }
    },
    mounted () {
      this.notificationTimer = setTimeout(() => {
        console.log('call trigger close ' + this.notification.name)
        this.triggerClose(this.notification)
      }, this.delay)
    }
  }
</script>

thanks for the help

Upvotes: 0

Views: 610

Answers (1)

Linus Borg
Linus Borg

Reputation: 23978

my small fiddle from back in the days is still making the rounds I see :D

That fiddle is still using Vue 1. In Vue 2, you have to key your list elements, and you tried to do that.

But a key should be a unique value identifying a data item reliably. You are using the array index, which does not do that - as soon as an element is removed, indices of the following items change.

That's why you see the behaviour you are seeing: Vue can't reliably remove the right element because our keys don't do their job.

So I would suggest to use a package like nanoid to create a really unique id per notification - but a simple counter would probably work as well:

let _id = 0
class NotificationStore {
  constructor () {
    this.state = {
      notifications: []
    }
  }
  addNotification (notification) {    
    this.state.notifications.push({ ...notification, id: _id++ })
  }
  removeNotification (notification) {
    console.log('remove from store')
    this.state.notifications.splice(this.state.notifications.indexOf(notification), 1)
  }
}

and in the notification component:

<notification-message 
  v-for="(notification, index) in notifications" 
  :notification="notification" 
  :key="notification.id"
  v-on:closeNotification="removeNotification"
></notification-message>

Upvotes: 2

Related Questions