I'm finding some strange behavior with JSON in a Vuex getter: it seems it is contributing to a pass-by-reference type of problem. For context - I'm developing a music app, which will have multiple "scenes", which each include collections of "tracks" (similiar to Ableton Live).
Here's my getter:
newTrack: state => {
let newTrack = JSON.parse(JSON.stringify(state.newTrackDefaults))
return newTrack
},
Here's the object it refers to:
newTrackDefaults: {
tune: [],
// and other properties
},
And then it is called by an action:
setUpNewScene: context => {
let newScene = JSON.parse(JSON.stringify(context.state.newSceneDefaults))
let newTrack = context.getters.newTrack
console.log(newTrack) // this reveals that the problem is from the getter
newScene.tracks.push(newTrack)
context.commit('addNewScene', newScene)
}
So the problem with this code is, when I add items (pitch-references) to a track on the first scene, then added a new scene, the new scene automatically receives the same track as the first scene. This is reflected in the Vuex state (according to the DevTool), not just the rendering. Also, when the tracks on the first scene are updated by the user, the tracks on the new scene change accordingly. So instinctively, this feels like a pass-by-reference type of error.
Through various console.log
experiments, I found that the getter was returning the "filled" track. It's fixable by skipping the getter and writing the action as:
setUpNewScene: context => {
let newScene = JSON.parse(JSON.stringify(context.state.newSceneDefaults))
let newTrack = JSON.parse(JSON.stringify(context.state.newTrackDefaults))
console.log(newTrack) // this works fine
newScene.tracks.push(newTrack)
context.commit('addNewScene', newScene)
}
... so though I have a fix, I'm puzzled as to the original behavior. Would the getter interfere with the JSON
functions? Or what else might be going on to cause this?
Vuex getters are cached.
https://vuex.vuejs.org/en/getters.html
Like computed properties, a getter's result is cached based on its dependencies, and will only re-evaluate when some of its dependencies have changed.
If their dependencies don't change, getters return their last returned value. Presumably, you aren't mutating newTrackDefaults
. This means that the getter newTrack
will never update. Which means context.getters.newTrack
is returning the same object every time. Therefore all the "new" tracks you think you're creating are actually the same track.
When dealing with default values, I like to create a function which returns the default values. Then I can call the function whenever and wherever I want, no funky caching business involved.
function newTrackDefaults() {
return {
tune:[],
// and other properties
}
}
setUpNewScene: context => {
let newScene = JSON.parse(JSON.stringify(context.state.newSceneDefaults))
let newTrack = newTrackDefaults()
newScene.tracks.push(newTrack)
context.commit('addNewScene', newScene)
}
Fun fact: Getters can return functions, so you can also fix it like this.
newTrack: state => () => {
let newTrack = JSON.parse(JSON.stringify(state.newTrackDefaults))
return newTrack
},
Now you need to call the getter, because it's a function now.
let newTrack = context.getters.newTrack()
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