0

I try to create a global snackbar to trigger every times an axios http error occurs, I followed this tutorial : https://dev.to/stephannv/how-to-create-a-global-snackbar-using-nuxt-vuetify-and-vuex-1bda

But I don't want use Nuxt, only Vue, Vuex and Vuetify, so I tried to created a plugin available everywhere, but I get these errors : [vuex] unknown mutation type: snackbar/showMessage and TypeError: Cannot read property '$snackbar' of undefined.

Here is my code :

src/main.js

import Vue from 'vue'
import router from "@/router";
import "./filters/filters";

import App from './App.vue'

import vuetify from "@/plugins/vuetify";

import VueSignaturePad from 'vue-signature-pad';
import axios from "axios";

import store from "@/store/snackbar";
import snackbarPlugin from "@/plugins/snackbar";

Vue.prototype.$http = axios;

Vue.config.productionTip = false

Vue.use(snackbarPlugin, { store });
Vue.use(VueSignaturePad);

axios.interceptors.response.use(
    function (response) {
        return response;
    },
    function (error) {
        // handle error
        if (error.response) {
            this.$snackbar.showMessage({ content: error.response.data, color: 'error', timeout: 10000 })
        }
    });

new Vue({
    router,
    store,
    vuetify,
    render: h => h(App)
}).$mount('#app')

src/store/snackbar.js

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        content: '',
        color: '',
        timeout: ''
    },
    mutations: {
        showMessage (state, payload) {
            state.content = payload.content
            state.color = payload.color
            state.timeout = payload.timeout
        }
    }
});

export default store;

src/plugins/snackbar.js

const snackbarPlugin = {
    install: (Vue, {store}) => {
        if (!store) {
            throw new Error('Please provide vuex store.');
        }

        Vue.prototype.$snackbar = {
            showMessage: function ({
                                       content = '',
                                       color = '',
                                       timeout = ''
                                   }) {
                store.commit(
                    'snackbar/showMessage',
                    {content, color, timeout},
                    {root: true}
                );
            }
        };
    },
};

export default snackbarPlugin;

src/utils/Snackbar.vue

<template>
    <v-snackbar v-model="show" :color="color" elevation="20">
        {{ message }}
        <v-btn color="accent" @click.native="show = false">
            <v-icon>close</v-icon>
        </v-btn>
    </v-snackbar>
</template>

<script>
export default {
    data() {
        return {
            show: false,
            message: "",
            color: "",
            timeout: 5000
        };
    },
    created () {
        this.$store.subscribe((mutation, state) => {
            if (mutation.type === 'snackbar/showMessage') {
                this.message = state.snackbar.content
                this.color = state.snackbar.color
                this.timeout = state.snackbar.timeout
                this.show = true
            }
        })
    }
};
</script>

src/App.vue

<template>
    <v-app>
        <Snackbar></Snackbar>
        <side-bar/>
        <Header/>
        <Main/>
    </v-app>
</template>

<script>
    import Snackbar from "@/utils/Snackbar";
    import SideBar from "@/components/layouts/SideBar";
    import Header from "@/components/layouts/Header";
    import Main from "@/components/layouts/Main";

    export default {
        name: 'App',
        components: {
            Snackbar,
            SideBar,
            Header,
            Main
        },
        beforeMount() {
            console.log('app')
            this.getRole()
            this.getEtats()
        },
        methods: {
            getRole() {
                this.$http.get('/rol')
                    .then(response => {
                        let agent = {}
                        agent.role = response.data.data.role
                        agent.username = response.data.data.username
                        localStorage.setItem('badge.agent', JSON.stringify(agent))
                    })
                    .catch(function (error) {
                        console.log(error)
                    })
            },
            getEtats() {
                this.$http.get('/nyx/badge/demande/accesnro/etats')
                    .then(response => {
                        localStorage.setItem('badge.etats', JSON.stringify(response.data.data))
                    })
                    .catch(function (error) {
                        console.log(error)
                    })
            }
        }
    }
</script>

I purposely throw a 404 error on the /role route to bring up the snackbar but the errors occurs.

Please do you have any idea what's wrong when i call this.$snackbar.showMessage ({content: error.response.data, color: 'error', timeout: 10000 }) in main.js ?

Oleksii Zelenko
  • 2,311
  • 3
  • 7
  • 21
McFlooz
  • 15
  • 7

1 Answers1

0

Because you do not use Vuex modules (https://vuex.vuejs.org/guide/modules.html), you should call commit without snackbar prefix:

store.commit('showMessage',{
    content, color, timeout}
);

Check this post (Using vuejs plugin on the main.js file). There is an explanation, how to use plugin in main.js and App at the same time.

main.js

import Vue from "vue";
import router from "@/router";
import "./filters/filters";

import App from "./App.vue";

import vuetify from "@/plugins/vuetify";

import VueSignaturePad from "vue-signature-pad";
import axios from "axios";

import store from "@/store/snackbar";
import snackbarPlugin from "@/plugins/snackbar";

Vue.prototype.$http = axios;

Vue.config.productionTip = false;

Vue.use(snackbarPlugin, { store });
Vue.use(VueSignaturePad);

axios.interceptors.response.use(
  function(response) {
    return response;
  },
  function(error) {
    // handle error
    if (error.response) {
      snackbarPlugin.showMessage({
        content: error.response.data,
        color: "error",
        timeout: 10000
      });
    }
  }
);

new Vue({
  router,
  store,
  vuetify,
  render: h => h(App)
}).$mount("#app");

snackbar.js

import store from "../store/snackbar";

const snackbarPlugin = {
  showMessage: ({ content = "", color = "", timeout = "" }) => {
    store.commit("snackbar/showMessage", { content, color, timeout }, { root: true });
  },
  install: (Vue, { store }) => {
    if (!store) {
      throw new Error("Please provide vuex store.");
    }
    Vue.prototype.$snackbar = snackbarPlugin;
  }
};

export default snackbarPlugin;

main.js

  • Thanks for your answer. In `main.js` i call the plugin `this.$snackbar.showMessage({ content: error.response.data, color: 'error', timeout: 10000 })`, the one that is supposed to call `store.commit ('snackbar / showMessage',{content, color, timeout},{root: true}); ` Isn't that supposed to work? – McFlooz Feb 09 '21 at 20:49
  • This call `this.$snackbar.showMessage({ content: error.response.data, color: 'error', timeout: 10000 })` is successful. The problem is with part `snackbar/showMessage` . The `snackbar` represent namespaced vuex module and `showMessage` name of your mutation. But you set your `showMessage` globally in your store. So vuex return error: `[vuex] unknown mutation type: snackbar/showMessage` You can easily call just `showMessage` or create snackbar module. – Václav Procházka Feb 09 '21 at 22:46
  • I added this at my store : **snackbar: { namespaced: true,** But I have still this error : `TypeError: Cannot read property '$snackbar' of undefined at eval (main.js:45)` in main.js. Maybe I can't use the key word `this` at this location to call my plugin ? Can you show me what do you think please ? – McFlooz Feb 09 '21 at 23:19
  • I updated my answer above. Hope, it will help. – Václav Procházka Feb 10 '21 at 07:25