Home > other >  Vue.js 2: return statement returning multiple values in parentheses (watch & computed)
Vue.js 2: return statement returning multiple values in parentheses (watch & computed)

Time:07-14

EDITED: Added more code for context as requested by the community.

I have a Vue.js 2 app that I am now maintaining where there is a computed property. This computed property has a return statement that is returning several data properties in a comma-separated format, and the entire group is wrapped in parentheses.

I tried looking at both Javascript and Vue.js 2 documentation, and I can't figure out what this is doing. In Javascript, if you wanted to return multiple values, you would return an array with the values or an object with multiple properties.

Here is the other weird thing. This "compoundFormProperty" computed property isn't used in the component template! There is a watcher on this "compoundFormProperty" property. I searched the entire code base, and "compoundFormProperty" only exists in this one component. Since it's not used by the component template, I think it is being used to track some internal state (I think it's used to check if the form is valid). The rest of the computed properties have get() and set() calls, so they appear to reference a Vuex store that stores the data.

<script>
    import { mapState } from "vuex";

    export default {
        name: "DeliveryAddress",
        components: {},
        props: {
            countries: Array,
            usStates: Array,
            canStates: Array,
            errorsCount: Number,
            isEdit: Boolean,
            selectedAddress: Boolean,
            sameAsBilling: Boolean
        },
        data: function() {
            return {
                sameAddress: false,
                saveAddress: false,
                nameEmpty: true,
                nameTouched: false,
                addressEmpty: true,
                addressTouched: false,
                cityEmpty: true,
                cityTouched: false,
                postalTouched: false,
                invalidDeliveryPostal: true,
                invalidBillingPostal: true,
                formTouched: false,
                storedInfo: {},
                cancel: false,
                editInvalid: false,
                saveInProgress: false,
                sameBilling: this.sameAsBilling
            };
        },
        computed: {
            formInvalid: {
                get() {
                    if (this.selectedAddress) {
                        return false;
                    }
                    return (
                        this.formTouched === false ||
                        this.nameEmpty ||
                        this.cityEmpty ||
                        this.addressEmpty ||
                        this.invalidDeliveryPostal
                    );
                }
            },
            compoundFormProperty: function() {
                return (
                    this.deliveryName,
                    this.deliveryCity,
                    this.deliveryAddress,
                    this.deliveryState,
                    this.deliveryCountry,
                    this.deliveryPostal,
                    Date.now()
                );
            },
            ...mapState([
                "name",
                "city",
                "address",
                "addressTwo",
                "country",
                "state",
                "postal",
                "cartObj",
                "accountObj"
            ]),
            cartObj: {
                get() {
                    return this.$store.state.cartObj;
                },
                set(value) {
                    this.$store.commit("setCart", value);
                }
            },
            deliveryName: {
                get() {
                    return this.$store.state.deliveryName;
                },
                set(value) {
                    this.$store.commit("setDeliveryName", value);
                }
            },
            deliveryAddress: {
                get() {
                    return this.$store.state.deliveryAddress;
                },
                set(value) {
                    this.$store.commit("setDeliveryAddress", value);
                }
            },
            deliveryAddressTwo: {
                get() {
                    return this.$store.state.deliveryAddressTwo;
                },
                set(value) {
                    this.$store.commit("setDeliveryAddressTwo", value);
                }
            },
            deliveryCity: {
                get() {
                    return this.$store.state.deliveryCity;
                },
                set(value) {
                    this.$store.commit("setDeliveryCity", value);
                }
            },
            deliveryState: {
                get() {
                    return this.$store.state.deliveryState;
                },
                set(value) {
                    this.$store.commit("setDeliveryState", value);
                }
            },
            deliveryCountry: {
                get() {
                    return this.$store.state.deliveryCountry;
                },
                set(value) {
                    this.$store.commit("setDeliveryCountry", value);
                }
            },
            deliveryPostal: {
                get() {
                    return this.$store.state.deliveryPostal;
                },
                set(value) {
                    this.$store.commit("setDeliveryPostal", value);
                }
            },
            accountObj: {
                get() {
                    return this.$store.state.accountObj;
                },
                set(value) {
                    this.$store.commit("setAccount", value);
                }
            }
        },
        watch: {
            compoundFormProperty() {
                if (this.sameBilling || this.selectedAddress) {
                    this.$emit("change", { errors: false });
                } else if (
                    this.deliveryName &&
                    this.deliveryCity &&
                    this.deliveryAddress &&
                    this.deliveryState &&
                    this.deliveryCountry &&
                    this.invalidDeliveryPostal === false
                ) {
                    this.$emit("change", { errors: false });
                } else if (
                    this.isEdit &&
                    this.deliveryName &&
                    this.deliveryCity &&
                    this.deliveryAddress &&
                    /^[-/\w ]{2,12}$/.test(this.deliveryPostal)
                ) {
                    this.editInvalid = false;
                } else {
                    this.$emit("change", { errors: true });
                    this.editInvalid = true;
                }
            }
            //Other watchers
        }
    }
</script>

CodePudding user response:

JavaScript functions can't return multiple values. Expressions delimited by comma operators are evaluated to the last one, i.e. compoundFormProperty returns Date.now().

It could be a mistake with comma-separated expression being returned instead of an array. But here it looks like a hack to trigger reactivity in computed property.

Due to how Vue reactivity works, deliveryName, etc. properties are marked to be tracked when accessed on this. compoundFormProperty will run on any changes in instance properties that are accessed inside computed property.

The explicit and not hacky way is to rewrite this as a collection watcher:

data() {
  return { compoundFormProperty: null, ... }
},
created() {
  this.$watch(
    ['deliveryName', ...],
    () => {
      this.compoundFormProperty = Date.now()
    },
    { immediate: true }
  );
}

The result may be somewhat different depending on the use, a watcher with immediate causes compoundFormProperty value to be assigned immediately, while computed property is evaluated the first time when it's accessed.

UPD: the combination of compoundFormProperty computed property and watcher in original post is a very hacky way to write this.$watch with array input, likely because a developer wasn't aware of it, or the code derived from Vue 1.x that didn't have specific functionality to shallowly watch collections. Date.now() value is efficiently ignored and used to not cache a computed.

  • Related