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: }
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!