Vue.js Computed Property Gotcha

I spent a few hours today trying to track down why a change password form I created wasn’t working. Ultimately, it turned out to be a surprising behavior from the way Vue.js (v2.6.14) computed properties are refreshed.

First, let’s have a quick reminder about how computed properties work. Each computed property runs some code and returns a result as the current value of that property. Vue will cache the value, and automatically re-compute it only when some data it depends upon changes. Normally, this works perfectly, and is completely transparent to the end user. Somehow, Vue just knows which other properties it depends upon.

So, on to my problematic example where it doesn’t just know. Let’s start with a simplified version of the component’s code:

01: computed: {
02:     canSubmit: function() {
03:         result = this.password && this.passwordConfirm;
04:         console.log(`canSubmit() -> #{result}`);
05:         return result;
06:     }
07: }
08: 
09: data: function() {
10:     return {
11:         password: undefined,
12:         passwordConfirm: undefined
13:     }
14: }

Seems pretty straight-forward, no? Well, it doesn’t work.

I have an event handler (not shown) which changes this.password and this.passwordConfirm each time the user types in an input element. For each letter the user types in the “password” box, you see the console statement. However, no matter what the user types into the “confirm” box, the console statement never appears.

The problem, it turns out, was on line 3:

    result = this.password && this.passwordConfirm;

Since this appears inside a computed property, behind the scenes Vue.js is trying to work out what other properties are being used so that it can figure out when this property will need to be refreshed. You would expect it would have have something like this in its dependency graph:

    canSubmit -> password, passwordConfirm

Except, it doesn’t. For some reason, when both this.password and this.passwordConfirm appear on the same line, Vue.js fails to recognize the dependency on this.passwordConfirm, and produces only this dependency graph:

    canSubmit -> password

Therefore, you see the console statements only when this.password changes, and not when this.passwordConfirm changes.

The fix is pretty easy. Just change canSubmit to this:

canSubmit: function() {
    p = this.password
    c = this.passwordConfirm
    return p && c
}

Having the references to this.password and this.passwordConfirm on separate lines appears to do the trick, and causes Vue.js to produce the expected dependency graph.


Why is this happening? To be honest, I don’t know. Here are a few theories…

The method Vue.js uses to detect dependencies:

  1. doesn’t work with multiple properties on the same line
  2. gets confused by the similarity of the two names
  3. get confused by the logical operators

To be honest, I have a hard time accepting that any of these are true, but they’re the only plausible explanations I can come up with. If anyone has a better theory, please leave it in the comments below!

2 thoughts on “Vue.js Computed Property Gotcha

  1. Keith Miner says:

    I wonder if on line 3 Vue is treating it like an if statement. If the first part has not changed (this.password) it does not look at the second part (this.passwordConfirm) and since they don’t change at the same time you don’t see the console statement when this.passwordConfirm changes. You can test this by switching the order on line 3 to this.passwordConfirm && this.password

    Like

    1. Come to think of it… I think you’re right! The property accessor probably has the code in it that “registers” the property as being an “input”. If a given property isn’t accessed when the value is first computed Vue.js has no way to figure out that it should pay attention to that property. In my first example, since `confirmPassword` isn’t accessed the first time through, Vue.js never realizes it should be paying attention to that property!

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s