Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getter not reactive when updating the property of an object in Vuex

I am experiencing a few issues regarding reactivity in my application.

I have a Vuex store where I have an object 'songs' containing 'song' objects where the key is their id. For each song object within, I have various properties, with the one in question being 'playing' a boolean value.

e.g.

songs = {
   2: {playing: false},
   7: {playing: true)
}

Within my application I have a list, using v-for for all these songs with a button that toggles the playing property of the song. When the button is pressed I dispatch the action I have created passing in the songid associated with the button that has been pressed

this.$store.dispatch('toggleSongPlaying', {songid: songid})

Depending on whether the song is playing or not, I wish to display the appropriate icon.

My simplified vuex store for my issue consists of the following:

const state = {
  songs: {}
};

const mutations = {
  update_song_playing(state, payload) {
     var songObj = state.songs[payload.songid]
     var playingBool = !songObj.playing
     Vue.set(songObj, 'playing', playingBool)
  }
};

const actions = {
  toggleSongPlaying({ commit }, payload) {
     commit("update_song_playing", payload)
  }
};

const getters = {
  getSongs: state => state.songs
};

The setting functionality is working fine, the issue is with reactivity. When the toggle button is pressed, the icon change does not occur. Triggering a rerender does however result in the icon changing. Within my component, I am using mapGetters like so:

computed: {
    ...mapGetters({
      songs: "getSongs"
    })
}

Is there something I am missing in order to get reactivity to work correctly with this nested object?

Thanks for any suggestions!

EDIT - Template code for list and icons

<v-list dense>
   <v-subheader>Songs List</v-subheader>
   <v-list-item-group v-model="activeSong" color="primary">
         <v-list-item
              v-for="(song, songID) in songs"
              :key="songID"
         >
               <v-icon class="mr-3" @click.stop="toggleSongPlaying(songID)">{{song.playing ? 'mdi-pause' : 'mdi-play'}}</v-icon>

                // song details etc...
                <v-list-item-content>
                    <v-list-item-title>
                    </v-list-item-title>
                </v-list-item-content>
                    
          </v-list-item>
   </v-list-item-group>
</v-list>
methods: {
   toggleSongPlaying(songid) {
       this.$store.dispatch('toggleSongPlaying', {songid: songid})
   }
},

EDIT2 In a separate component in created, I populate the songs object with a song like so

created() {
    var songid = "12"
    var song = {
        length: ...,
        playing: false,
    }

    var payload = {
        song: song,
        songid: songid
    }

    this.$store.dispatch('addSong', payload)
},

Within Vuex

Action:

addSong({ commit }, payload) {
   commit("add_song", payload);
}

Mutator:

add_song(state, payload) {
    state.songs[payload.songid] = payload.song
},
like image 241
Gecko29 Avatar asked Sep 08 '20 01:09

Gecko29


People also ask

Are Vuex getters reactive?

Vuex getter is not reactive.

Are getters cached Vuex?

Vuex allows us to define "getters" in the store. You can think of them as computed properties for stores. As of Vue 3.0, the getter's result is not cached as the computed property does.

How do I update my Vuex data?

To update existing data, you can do so with the update method. Following example is some simple Vue Component handling data update with form data. The update method takes where condition and data as payload. where condition can be a Number or a String representing the value of the primary key of the Model.

Is it possible to mutate an array in Vuex state?

I am stuck with a problem - Got an array object in Vuex state that is not reactive on mutation. Yes, using the Vue.set for mutating the array. Guessing it is because of the way the array object is declared in the state. Tried with a regular object and it works fine.

Which part of a Vue mutation does not trigger reactivity?

In the mutation the Vue.set or the state.plans = Object.assign (state.plans, payload) - isn’t triggering the reactivity. I am trying to understand why.

How to splice the value in at an index in Vue?

The documentation provides 2 workarounds for this: // 1. use purpose built vue method: Vue.set (state.laptops, index, laptop) // 2. splice the value in at an index: state.laptops.splice (index, 1, laptop)

Does currentplan getter ever update?

The currentPlan getter returns a function, and it doesn’t depend on anything really. So it won’t ever update - and if it did, all it would do is return a fresh function. So I’m not sure what you are expecting here First - Thank you, @LinusBorg for your reply! Appreciate it!!


2 Answers

The way you are assigning songs to your state is not reactive

For Objects

Vue cannot detect property addition or deletion

Change your add_song mutation to replace the songs state property with a new one, including the new song. This treats songs as immutable

add_song: (state, { songid, song }) => {
  state.songs = {
    ...state.songs,
    [ songid ]: { ...song } // break any object references, thank me later
  }
},

Now you don't need to use Vue.set because the payload property has been added reactively. Your update_song_playing can simply be

update_song_playing: (state, { songid }) => {
  const song = state.songs[songid];
  if (song) {
    song.playing = !song.playing;
  }
}

You can also use Vue.set in your add_song mutation but I've always felt Flux-based stores work best with immutable data.

like image 149
Phil Avatar answered Nov 08 '22 19:11

Phil


Call $forceUpdate() after calling your dispatch such as:

<v-icon class="mr-3" @click.stop="toggleSongPlaying(songID), $forceUpdate()">{{song.playing ? 'mdi-pause' : 'mdi-play'}}</v-icon>
like image 26
Max Avatar answered Nov 08 '22 19:11

Max