Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

VueJS - VueX and flash messages

I use VueJS 2, VueX, NuxtJS and Vue-Snotify (artemsky/vue-snotify) for flash notifications.

It may not be the correct use of VueX, but I'd like to dispatch the errors caught in a try/catch.

try {
    throw new Error('test')
} catch (error) {
    this.$store.dispatch('errorHandler', error)
}

Then the dispatch, with VueX should display the notifications with Snotify-View with a loop if there are several errors.

actions: {
    async errorHandler (error) {
        this.$snotify.error(error)
        // and if multiple errors, then while on error
    }
}

What do you think and how to recover the instance of $snotify in VueX?

like image 741
pirmax Avatar asked May 07 '18 13:05

pirmax


1 Answers

Bad

I realize that the app instance is injected in the initialization of the Vue store. So, you can access through this.app.$snotify to the Snotify services where you want.

Also, another place at the store that receive the Nuxt context as the second argument is the nuxtServerInit[2]. Thus, you could access to the service using something like the next snippet.

actions: {
    nuxtServerInit ({ state }, { app }) {
        // Workaround
        state.Snotify = app.$snotify // inject the context in the store
    },
    // Then, you can called as you want
    // There is not necessity to prefix the method with async keyword
    errorHandler ({ state }, message) {
        state.Snotify.error(message)
    }
}

Good

To my point of view the store doesn't have any responsibility to handler the presentation behavior of the data. Thus, the only goal of the store, in this case, is to pass the message between components to be displayed in other as a flash message, with Snotify in this particular case. So to conclude my appreciation of the statement, I think that actions only are responsible to change the state of the store as a unique source of truth, by definition.

You must use a mutation instead and store only the Object. Then, in your view or HOC (High Order Component) set up the presentation logic using Snotify where it's available. To support my answer I strong recommend this library due to it has a better API to communicate with Vuex and also it has a good presentation interface (UI/UX). Disclaimer: I'm not saying that this one is better than Snotify, each one was built to satisfy purpose slightly different, at least, as far as UX concept is concerned. Both are great and could be used either way to illustrate this use case.


I will change your second snippet for:

state: {
    flash: null
}
mutations: {
    // Just extract message from the context that you are set before
    SET_ERROR (state, { message, title = 'Something happens!' }) {
        state.flash = { 
            type: 'error',
            title, 
            message
        }
    },
    FLUSH_FLASH (state) {
        state.flash = null
    }
}

And also, I will add this on some view/layout/component/HOC (I used SFC way for its most common use)

<template>
    <vue-snotify />
    ...
</template>

<script>
   export default {
       // I use fetch because this is the lifecycle hook that executes 
       // immediately before page render is sure. And its purpose is to fill
       // the store before page render. But, to the best of my knowledge, 
       // this is the only place that you could use to trigger an immediately
       // executed function at the beginning of page render. However, also
       // you could use a middleware instead or "preferably use a wrapper 
       // component and get leverage of component lifecycle and use `mounted`" [4]
       fetch({ app, store }) {
           if (store.state.flash) {
               const { type, title, message: body } = store.state.flash
               const toast = app.$snotify[type](body, title)

               toast.on('destroyed', (t) => { store.commit('FLUSH_FLASH') })
           }
       },
       data: () => ({
           ...
       })
</script>

Maybe, the code above is not fully functional to you, but I suggest that you should test a similar approach and fit to your needs.

EDIT

I want to point out another improvement to my answer, based on my last update related with components lifecycle. At the beginning, I actually want to put the message inside the component mounted method but then I thought that Nuxt pages have a different lifecycle until I see this example and realize that Nuxt still been Vue on the background. Thus, any page actually is also a component by definition too. Then you could do a much semantic approach as well.

<template>
    <vue-snotify />
    ...
</template>

<script>
   export default {
       data: () => ({
           ...
       }),
       // Show the flash at the beginning when it's necessary
       mounted: {
           if (this.notification) {
               const { type, title, message: body } = this.notification
               const toast = this.$snotify[type](body, title)

               toast.on('destroyed', (t) => { this.$store.commit('FLUSH_FLASH') })
           }
       },
       computed: {
           notification () {
               return this.$store.state.flush
           }
       }
</script>

References

[1] https://zendev.com/2018/06/07/async-data-options-in-vue-nuxt.html

[2] https://twitter.com/krutiepatel/status/1000022559184764930

[3] https://github.com/artemsky/vue-snotify/blob/master/example/src/App/app.ts

[4] https://medium.com/@fishpercolator/implementing-a-global-snackbar-in-nuxt-js-vuetify-using-vuex-a92b78e5651b

like image 196
monk Avatar answered Sep 30 '22 04:09

monk