VueJS - Skip watcher's first change

18,610

Solution 1

Badgy's response seems less intrusive and less prone to error.

The battle-proven method used in our project, particularly for tracking changes, looks like this:

export default {
  data: () => {
    dataLoaded: false,
    valueThatChanges: 'defaultValue',
  },
  mounted () {
    let self = this
    loadData().then((result) => {
      self.valueThatChanges = result
      self.$nextTick(() => { //with this we skip the first change
        self.dataLoaded = true
      })
    })
  },
  methods: {
    loadData() {
      //your implementation
      return result
    }
  },
  watch: {
    valueThatChanges: function(newValue, oldValue) {
      if (this.dataLoaded) {
        //send tracking change
        console.log('Value was changed from ' + oldValue + ' to ' + newValue + ' via interaction, not by loading')
      }
    }
  }
}

Solution 2

You could use the instance's $watch() method to start watching a data element at any point in your component. This way, the watcher wouldn't be instantiated right on the Vue instance's instantiation, as the Docs specify:

"The Vue instance will call $watch() for each entry in the [watch] object at instantiation."

So, you'd probably be looking for this:

data: {
  return() {
    data: null,
  };
},

mounted() {
  api.fetchList().then((response) => {
    this.data = response.dataFetched
    this.$watch('data', this.dataWatcher);
  });
},

methods: {
  dataWatcher() {
    // Code won't execute after this.data assignment at the api promise resolution
  },
}

In simple cases like this, Badgy's response is more direct, despite you could avoid the doneFetching variable. I'd use the $watch() if I needed more control: It actually returns an "unwatch" function you could call to delete it (Check it out).

Keep in mind also that watchers should be used for edge cases, and avoid mutating watched variables inside other watchers. This may harm Vue reactivity's "orthogonality" between data and methods if not used carefully.

Solution 3

You should simply declare a Boolean Variable which defines if your data fetching is done. By default you set it to false doneFetching: false and once your fetching Logic is done you call this.doneFetching = true.

After that all you have to do in your watcher is a clean and simply if(this.doneFetching){...} This simple logic should prevent your watcher logic to get triggered before you want it.

Share:
18,610

Related videos on Youtube

Pablo Villalba
Author by

Pablo Villalba

I just like to writte code. Know about PHP, Java, Python, Javascript, HTML, XML and a few more

Updated on June 04, 2022

Comments

  • Pablo Villalba
    Pablo Villalba almost 2 years

    I'm creating a component in VueJS for an app I'm making, it has some watchers to apply logic to other parts of the component when my var changes. Once the component is initialized, it still needs to be set by some data from the server coming after a few events done by the user via Axios. This data gets to the component from an event emmited by the main app. Now the thing is that this var usually changes (not always), but I don't want that logic to be applied this first time, so I decided to set a flag and check it in the watcher to return , but it's not happening: as soon as I set that flag back to true (it checks for !this.flag), the watcher gets triggered anyways. Here's the code:

    data(){
        return {
            isSet: false,
            myVar: {
                first: 0,
                second: 0
            }
        }
    },
    watch: {
        'myVar.first': {
            handler: function(newVal, oldVal){
                if(!this.isSet || other.condition){return}
                console.log('first triggered');
            },
            immediate: false
        },
        'myVar.second': {
            handler: function(newVal, oldVal){
                if(!this.isSet || other.condition){return}
                    console.log('second triggered');
            },
            immediate: false
        }
    },
    methods: {
        setBus(){ // gets called on created(){}
            let self = this;
            Bus.$on('my-event', function(c){
                self.doFirstSet(c);
            });
        },
        doFirstSet(c){
            // do other setting
            if(c.someCondition){
                this.doExtraConfig(c);
            }
            this.isSet = true; // at this point is when the watchers get triggered
        },
        doExtraConfig(c){
            // do more stuff
            if('first' in c){
                this.myVar.first = c.first;
            }else if('second' in c){
                this.myVar.second = c.second;
            }
            console.log("watchers don't get triggered yet");
        }
    }
    

    Any idea of how to stop them to fire when the flag changes?

  • gotson
    gotson about 4 years
    This is better than using a boolean, since the watch callbacks could be called asynchronously after the boolean has been changed.
  • Cave Johnson
    Cave Johnson almost 4 years
    I think the let self = this is not necessary. Arrow functions preserve the value of this from the outer scope.
  • Shadow The Kid Wizard
    Shadow The Kid Wizard over 3 years
    I agree with @yooouuri and gave a little boost to the author of this answer. :-)
  • wondra
    wondra over 2 years
    I am afriad it wont work in many cases, the watcher callback (unless immediate?) usually executes after the method setting flag is finished, not when the value acutally changed.