Amresh Venugopal
Amresh Venugopal

Reputation: 9549

Using conditional logic in computed properties fails to update

I have two fiddles: A, B (using Vuejs 2.2.4)

I have a computed property which can be changed programmatically (I am using the get and set methods).

Expectations:

  1. If the default parameter changes (this.message), the computed property (computedMessage) must change (default behaviour).

  2. If the secondary parameter changes (this.messageProxy), only then the computed property must reflect the secondary parameter.

Fiddle A works as expected but Fiddle B doesn't.

Error: The default behaviour (point 1) stops after the secondary parameter changes.

The only difference between the fiddles is a console statement in the computed property.


Background: I was trying to set a computed property programatically. The computed property is set like:

computedMessage: {
  get () {
    let messageProxy = this.messageProxy
    this.messageProxy = null
    console.log(messageProxy, this.messageProxy, this.message)
    return messageProxy || this.message
  },
  set (val) {
    this.messageProxy = val
  }
}

This allows me to set the value of computedMessage like:

this.computedMessage = 'some string'

If these lines:

get () {
  let messageProxy = this.messageProxy
  this.messageProxy = null
  return messageProxy || this.message
}

were to be replaced with:

get () {
  return this.messageProxy || this.message
}

then computedMessage can no longer get access to this.message the moment this.messageProxy is set.

By setting this.messageProxy to null I ensure that the

computedMessage = this.messageProxy

only if an assignment is made.

Upvotes: 8

Views: 8537

Answers (2)

Roy J
Roy J

Reputation: 43881

The actual problem with your code is that you are changing data values in your get function, and they are data values that trigger the re-computation of the get function. Don't do that. The get should just be computing a value based on other values. In this case, it should be

    get () {
      console.log(this.messageProxy, this.message);
      return this.messageProxy || this.message;
    },

With or without the console message, it will do what it is supposed to do.

Having re-checked your expectations, I see that you want the override to be cleared whenever the default message changes. You can do that with an additional watch:

var demo = new Vue({
  el: '#demo',
  data() {
    return {
      message: 'I am a great guy',
      messageProxy: null,
      someText: ''
    }
  },
  computed: {
    computedMessage: {
      get() {
        return this.messageProxy || this.message
      },
      set(val) {
        this.messageProxy = val
      }
    }
  },
  methods: {
    overrideComputed() {
      this.computedMessage = this.someText
    }
  },
  watch: {
    message: function() {
      this.messageProxy = null;
    }
  }
})
div {
  margin: 5px;
}
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.4/vue.min.js"></script>
<div id="demo">
  <p>This message must reflect value of input1</p>
  <div>
    {{ computedMessage }}
  </div>

  input1: <input type="text" v-model='message'>

  <div>
    <p>This will cause computed message to reflect input2</p>
    input2: <input type="text" v-model='someText'>
    <button @click='overrideComputed'>Override</button>
  </div>
</div>

PS: You don't really need a settable computed here. You could have overrideComputed set messageProxy directly.

Upvotes: 2

thanksd
thanksd

Reputation: 55644

The reference to this.message in the return statement isn't triggering computedMessage to update. This is because its location in the logical || statement makes it inaccessible. It's a gotcha documented in the Vue.js Computed Properties Documentation.

From the Docs:

status: function () {
    return this.validated
        ? this.okMsg
        : this.errMsg // errMsg isn't accessible; won't trigger updates to status
}

The workaround is to explicitly access dependencies:

status: function () {
    // access dependencies explicitly
    this.okMsg
    this.errMsg
    return this.validated
        ? this.okMsg
        : this.errMsg
}

So in your example add a reference to this.message:

get() {
  this.message
  let messageProxy = this.messageProxy
  this.messageProxy = null
  return messageProxy || this.message
}

The reason your first fiddle was working as expected was because the console.log call had this.message as a parameter.

Upvotes: 9

Related Questions