Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON Object copy in Vuex getter

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?

like image 366
drenl Avatar asked Apr 27 '18 22:04

drenl


1 Answers

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()
like image 179
Eric Guan Avatar answered Sep 24 '22 10:09

Eric Guan