Search code examples
javascriptgoogle-chromevue.jsvuexvuetify.js

V-snackbar does not display when I update the Vuex state


I can't seem to figure out why my <v-snackbar> component does not show when I update the Vuex state.

My setup is very simple: I have a snackbar in Snackbar.js, which is listening for changes in the state.

This Snackbar.js is imported as a child component in App.vue so that its global.

Next, I have a Test.vue which has a simple button. I expect that when I click the button, the State should be updated and the snackbar should render, but it doesn't.

By inspecting the Snackbar component in the Chrome Vue Devtools, I could see that the data actually makes it to the store, but somehow doesn't update the reactive props in Test.vue enter image description here

Here are the relevant codes:


Snackbar.vue

<template>
  <v-snackbar v-model="show" :top="top" multi-line rounded="pill">
    {{ text }}
    <v-btn text @click.native="show = false">
      <v-icon>close</v-icon>
    </v-btn>
  </v-snackbar>
</template>

<script>
import { mapState } from 'vuex'
export default {
  data () {
    return {
      show: false,
      text: '',
      top: true
    }
  },
  computed: {
    ...mapState(['snackbar'])
  },
  created: () => {
    this.unwatch = this.$store.watch(
      // watch snackbar state
      (state, getters) => getters.snackbar,
      () => {
        const text = this.$store.state.snackbar.text
        if (text) {
          this.show = true
          this.text = text
        } 
      }
    )
  },
  beforeDestroy () {
    this.unwatch()
  }
}
</script>

App.vue

<template>
  <v-app>
    <v-main>
      <!-- try to set a global snackbar -->
      <Snackbar/>
      <router-view/>
    </v-main>
  </v-app>
</template>

<script>
import Snackbar from '@/components/Snackbar'

export default {
  name: 'App',
  components: {
    Snackbar
  }
}
</script>


Test.vue

<template>
    <v-btn @click="passData">Show snackbar</v-btn>
</template>

<script>
import { mapActions } from 'vuex'
export default {
  name: 'Test',
  data: () => ({
    //
  }),
  computed: {},
  methods: {
    ...mapActions([
      'setSnackbar'
    ]),
    passData () {
      this.setSnackbar({
        text: 'Simple message',
        isActive: true
      })
    }
  }
}
</script>

Store/index.js

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    snackbar: {
      isActive: false,
      text: ''
    }
  },
  getters: {
    snackbar (state) {
      return state.snackbar
    }
  },
  mutations: {
    populateSnackbar (state, payload) {
      state.snackbar.isActive = payload.isActive
      state.snackbar.text = payload.text
    }
  },
  actions: {
    setSnackbar (context, payload) {
      context.commit('populateSnackbar', payload)
    }
  }
})

Solution

  • The above code will show snackbar for the first time due to created hook and from next time it will not work, this is due to Vue reactivity

    Here the state will get updated, but the data properties in snackbar component not received the updated data

    The snackbar component can be replaced by

    <template>
      <v-snackbar v-model="snackbar.show" :top="top" multi-line rounded="pill">
        {{ snackbar.text }}
        <v-btn text @click.native="$store.dispatch('setSnackbar', {text: '', isActive: false})">
          <v-icon>close</v-icon>
        </v-btn>
      </v-snackbar>
    </template>
    
    <script>
    import { mapState } from 'vuex'
    export default {
      computed: {
        ...mapState(['snackbar'])
      },