Search code examples
vue.jsnuxt.jsstatevuexstore

Vuex state updates break v-for reactivity


I am trying to make a Notification component in Nuxt using the built-in Vuex store. I have two components, Notifications.vue and Notification.vue to display the notifications. My store has two mutations, one for adding a notification and another for removing one.

Here are the relevant files from my project:

store/notifications.js

export const state = () => ({
    notifications: []
});

export const getters = {
    allNotifications: state => state.notifications
};

export const mutations = {
    PUSH_NOTIFICATION(state, notification) {
        state.notifications.push(notification);
    },
    REMOVE_NOTIFICATION(state, notificationToRemove) {
    // PROBLEM ---> Updates state but breaks reactivity
        state.notifications = [
            ...state.notifications.filter(notification => notification.id != notificationToRemove)
        ];
};

export const actions = {
    async pushNotification({ commit }, notification) {
        return new Promise((resolve, reject) => {
            notification = {
                ...notification,
                id: (Math.random().toString(36) + Date.now().toString(36)).substr(2)
            };
            commit('PUSH_NOTIFICATION', notification);
            resolve(notification.id);
        });
    },
    async removeNotification({ commit }, id) {
        return new Promise((resolve, reject) => {
            commit('REMOVE_NOTIFICATION', id);
            resolve();
        });
    }
};

plugins/notifications.js

export default ({ store }, inject) => {
    let notifications = {
        async notify(notification) {
            return await store.dispatch('notifications/pushNotification', notification);
        },
        async removeNotification(id) {
            console.log(`The notification with the id ${id} will be removed!`);
            await store.dispatch('notifications/removeNotification', id);
            return true;
        }
    };
    // This allows me to access the methods above from any component like `this.$notifications`
    inject('notifications', notifications);

};

components/Notifications.vue

<template>
    <div class="notifications">
        <button @click="test()">Notify</button><!-- This is to test adding new notifications -->
        <div class="notification-bounds">
            <Notification v-for="notification in notifications" :key="notification.id" :id="notification.id">{{ notification.content }}</Notification>
        </div>
    </div>
</template>

<script>
    export default {
        data() {
            return {
                notifications: this.$store.getters['notifications/allNotifications']
            };
        },
        methods: {
            test() {
                // This works therefore `PUSH_NOTIFICATION` does not break reactivity
                this.$notifications.notify({ content: 'Test' }); 
            }
        }
    };
</script>

components/Notification.vue

<template>
    <div class="notification">
        <slot></slot>
        <button @click="close()">Close</button>
    </div>
</template>

<script>
    export default {
        props: [
            'id'
        ],
        methods: {
            close(){
    // PROBLEM ---> This updates the state internally but it breaks reactivity so 
    // there is something wrong with the `REMOVE_NOTIFICATION` mutation.
                this.$notifications.removeNotification(this.id) 

            }
        }
    };
</script>

The problem is that the REMOVE_NOTIFICATION mutation breaks the reactivity of the v-for in Notifications.vue. How should I solve this?


Solution

  • Alright, the lack of reactivity is because you do fetch your notifications inside of data.
    data is aimed to be static and not recompute any changes, indeed.

    Try this one in your Notifications.vue file

    <script>
    export default {
      computed: {
        notifications() {
          return this.$store.getters['notifications/allNotifications']
        },
      },
    }
    </script>