2

According to this post, it shouldn't be a problem to watch a computed property. And yet my code isn't working.

<template>
    <div v-if="product" class="section">
        <form>
            <div class="control"><input type="text" class="input" v-model="title"></div>
            <div class="control"><input type="text" class="input" v-model="description"></div>
        </form>
    </div>
</template>

<script>
export default {
    data() {
        return {
            title: null,
            description: null
        }
    },
    computed: {
        product() {
            // const payload = { collection: 'products', id: this.$route.params.productId }
            // return this.$store.getters.objectFromId(payload)
            console.log('working')
            return { title: 'Awesome Title', description: 'Awesome Description' }
        }
    },
    watch: {
        product() {
            this.title = this.product.title,
            this.description = this.product.description
        }
    }
}
</script>

I'm expecting the watch to trigger when product is returned, but it doesn't.

I could set the properties in the computed property like so:

computed: {
    product() {
        const payload = { collection: 'products', id: this.$route.params.productId }
        const product = this.$store.getters.objectFromId(payload)
        this.title = product.title
        this.description = product.description
        return product
    }
}

But then the compiler gives me a warning: error: Unexpected side effect in "product" computed property

HJo
  • 1,902
  • 1
  • 19
  • 30
  • What are you trying to achieve? There may be a more straightforward solution to your problem – Enrico Jul 21 '18 at 20:10
  • I've updated my post to more clearly display intent. I'm trying to get a product and set some inital data() that are bound to some form inputs. I **could** set values within the compute function but I get this annoying side effect warning in the compiler. I believe it's bad practice to set properties in a get? – HJo Jul 21 '18 at 20:16
  • 1
    You would need to [use a watcher](https://codepen.io/Kradek/pen/XBpodo?editors=1010) with `immediate` set in order to get your watcher to fire on the initial render. – Bert Jul 21 '18 at 21:14
  • Maybe you really want title and description to be computed values with both getters and setters. – Bert Jul 21 '18 at 21:19
  • 1
    @HamishJohnson you don't use computed for that. Have a look at created/mounted vue.js hooks. Inside them you can load external data and attach it to the current data model – Enrico Jul 22 '18 at 00:52
  • 1
    @Enrico Yeah I have to agree with you there - not really sure why I chose computed in this instance. Thanks – HJo Jul 22 '18 at 09:38
  • @HamishJohnson have you come up with a solution to your problem? – Enrico Jul 22 '18 at 10:09
  • @Enrico I did, I put it in created() instead, as per your suggestion - if you want to answer the question I'll mark it as correct – HJo Jul 22 '18 at 10:19
  • @Bert: to me, your answer is spot on. I prefer it to the accepted answer, because this could allow given component's parameters being reactive beyond the creative or mounted life-cycle hooks. IMO it'd be nice, if you proposed it as a main answer. – rubebop Apr 05 '22 at 10:01

3 Answers3

2

Accordingly to OP's comments, his intention is to get and load some initial data. The common way to achieve this behavior is to place it inside created or mounted vuejs lifecycle hooks.

<template>
    <div v-if="product" class="section">
        <form>
            <div class="control"><input type="text" class="input" v-model="title"></div>
            <div class="control"><input type="text" class="input" v-model="description"></div>
        </form>
    </div>
</template>

<script>
export default {
    data() {
        return {
            title: '',
            description: ''
        }
    },

    created() {
        this.getInitialData();
        this.foo();

        console.log("created!");
    },

    methods: {
        getInitialData: function(){
            const payload = {
                collection: 'products', 
                id: this.$route.params.productId 
            };
            var product = this.$store.getters.objectFromId(payload);
            this.title = product.title;
            this.description = product.description;
        },
        foo: function(){// ...}
    },
}
</script>
Enrico
  • 408
  • 6
  • 13
1

Try the following:

watch: {
    product: {
        immediate: true,
        handler(value) { 
            updateCode();
        }
    }
}
linktoahref
  • 7,812
  • 3
  • 29
  • 51
0

Your structure is a bit all over the place. product is a computed, so it runs whenever it's source values change. (You have no control over when it runs.) It shouldn't have side effects (assignments this.description, this.title), or trigger network requests.

The code in product is fetching your source data. This belongs in methods, linked explicitly to a user action or a lifecyle event.

Why do you need to copy your data (this.description = product.description in watch:product)? Vue works best when you have your data (your app state) outside Vue, in a global variable say. Then your Vue components just transparently reflect whatever the app state is at a given moment.

Hope this helps.

bbsimonbb
  • 27,056
  • 15
  • 80
  • 110