82

I am learning Vue and facing a problem while using arrow function in computed property.

My original code works fine (See snippet below).

new Vue({
  el: '#app',
  data: {
    turnRed: false,
    turnGreen: false,
    turnBlue: false
  },
  computed:{
   switchRed: function () {
     return {red: this.turnRed}
    },
    switchGreen: function () {
     return {green: this.turnGreen}
    },
    switchBlue: function () {
     return {blue: this.turnBlue}
    }
  }
});
.demo{
  width: 100px;
  height: 100px;
  background-color: gray;
  display: inline-block;
  margin: 10px;
}
.red{
  background-color: red;
}
.green{
  background-color: green;
}
.blue{
  background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.4/vue.js"></script>
<div id="app">
  <div class="demo" @click="turnRed = !turnRed" :class="switchRed"></div>
  <div class="demo" @click="turnGreen = !turnGreen" :class="switchGreen"></div>
  <div class="demo" @click="turnBlue = !turnBlue" :class="switchBlue"></div>
</div>

However, after I change methods in computed property, the color will not change (though the turnRed value still switch between true and false successfully).

This is my code:

computed:{
    switchRed: () => {
        return {red: this.turnRed}
    },
    switchGreen: () => {
        return {green: this.turnGreen}
    },
    switchBlue: () => {
        return {blue: this.turnBlue}
    }
}

Do I use the wrong syntax ?

PJCHENder
  • 5,570
  • 3
  • 18
  • 35

6 Answers6

124

You are facing this error because an arrow function wouldn't bind this to the vue instance for which you are defining the computed property. The same would happen if you were to define methods using an arrow function.

Don’t use arrow functions on an instance property or callback (e.g. vm.$watch('a', newVal => this.myMethod())). As arrow functions are bound to the parent context, this will not be the Vue instance as you’d expect and this.myMethod will be undefined.

You can read about it here.

tony19
  • 125,647
  • 18
  • 229
  • 307
Amresh Venugopal
  • 9,299
  • 5
  • 38
  • 52
58

The arrow function lost the Vue component context. For your functions in methods, computed, watch, etc., use the Object functions:

computed:{
    switchRed() {
        return {red: this.turnRed}
    },
    switchGreen() {
        return {green: this.turnGreen}
    },
    switchBlue() {
        return {blue: this.turnBlue}
    }
}
ThatShawGuy
  • 1,363
  • 1
  • 14
  • 27
throrin19
  • 17,796
  • 4
  • 32
  • 52
  • 1
    The only thing I can add here is that if you have functions inside those functions, e.g. a callback, then using arrow function will make your codes cleaner because it uses this object from the enclosing context, which is vue instance now. – Qiulang Jan 25 '18 at 03:30
  • 1
    This is the most useful answer as it explains how to fix the issue. I would recommend this answer and the accepted answer as the actual "whole ideal" answer. – DrCord Mar 20 '18 at 19:31
  • One strange thing I noticed is that when you define a computed property with an arrow function and then you debug with chrome devtools it still appears as though 'this' is referencing the VueJS component context. It took me forever to figure out that arrow functions were causing my issues because when I put a 'debugger' statement inside my arrow function and then inspected 'this' all the component data was showing up but when I tried to console.log() my component data it was undefined. – Kevin Aud Oct 18 '18 at 14:39
8

You can achive this by deconstructing what you want from this:

computed:{
  switchRed: ({ turnRed }) => {red: turnRed},
  switchGreen:  ({ turnGreen }) => {green: turnGreen},
  switchBlue: ({ turnBlue }) => {blue: turnBlue}
}
Sebastian
  • 1,321
  • 9
  • 21
Marius Lian
  • 523
  • 7
  • 15
  • 8
    This is working because computed properties receive component instance as their first argument. So this is basically switchRed: (this) => {red: this.turnRed} – transGLUKator Mar 11 '21 at 10:01
5

And why not something simpler like this?

new Vue({
  el: '#app',
  data: {
    turnRed: false,
    turnGreen: false,
    turnBlue: false
  },
  methods:{
    toggle (color) {
      this[`turn${color}`] = !this[`turn${color}`];
    }
  }
});
.demo{
  width: 100px;
  height: 100px;
  background-color: gray;
  display: inline-block;
  margin: 10px;
}
.red{
  background-color: red;
}
.green{
  background-color: green;
}
.blue{
  background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.4/vue.js"></script>
<div id="app">
  <div class="demo" @click="toggle('Red')" :class="{red:turnRed}"></div>
  <div class="demo" @click="toggle('Green')" :class="{green: turnGreen}"></div>
  <div class="demo" @click="toggle('Blue')" :class="{blue: turnBlue}"></div>
</div>
petey
  • 16,914
  • 6
  • 65
  • 97
Soldeplata Saketos
  • 3,212
  • 1
  • 25
  • 38
4

When creating computed you do not use => , you should just use switchRed () {...

Take a look at snippet. Works as it should.

It applies to all computed,method, watchers etc.

new Vue({
  el: '#app',
  data: {
    turnRed: false,
    turnGreen: false,
    turnBlue: false
  },
  computed:{
   switchRed () {
     return {red: this.turnRed}
    },
    switchGreen () {
     return {green: this.turnGreen}
    },
    switchBlue () {
     return {blue: this.turnBlue}
    }
  }
});
.demo{
  width: 100px;
  height: 100px;
  background-color: gray;
  display: inline-block;
  margin: 10px;
}
.red{
  background-color: red;
}
.green{
  background-color: green;
}
.blue{
  background-color: blue;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.2.4/vue.js"></script>
<div id="app">
  <div class="demo" @click="turnRed = !turnRed" :class="switchRed"></div>
  <div class="demo" @click="turnGreen = !turnGreen" :class="switchGreen"></div>
  <div class="demo" @click="turnBlue = !turnBlue" :class="switchBlue"></div>
</div>
James Westgate
  • 11,306
  • 8
  • 61
  • 68
Marek Urbanowicz
  • 12,659
  • 16
  • 62
  • 87
3

I don't know if this will backfire in future but apparently arrow functions used in vue object properties, receive the this context as their 1st argument:

props: ['foo'],

data: (ctx) => ({
  firstName: 'Ryan',
  lastName: 'Norooz',
  // context is available here as well like so:
  bar: ctx.foo
}),

computed: {
  fullName: ctx => ctx.firstName + ' ' + ctx.lastName // outputs: `Ryan Norooz`
}

this way you can still access everything in the component context just like this !

Ryan Norooz
  • 1,358
  • 1
  • 12
  • 8