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
},
Vuex getter is not reactive.
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.
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.
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.
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.
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)
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!!
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.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With