Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Accessing computed properties of child components in Vue

What's the proper way to access computed properties of child components? Below is a simplified example of what I'm trying to achieve (also available on JSFiddle):

const FoobarWidget = {
    template: '<li>a: <input type="text" v-model="value.a" style="width:2em;"> b: <input type="text" v-model="value.b" style="width:2em;"> sum: {{this.sum}} <button @click="die">x</button></li>',
    props: {
        value: {
            type: Object,
            required: true,
        }
    },
    computed: {
        sum() {
            const s = Number(this.value.a) + Number(this.value.b)
            // WARNING eslint - vue:no-side-effects-in-computed-properties
            this.value.sum = s;
            return s;
        }
    },
    methods: {
        die() {
            this.$emit('die');
        }
    }
};

new Vue({
    el: '#app',
    data: {
        foobars: [{
            a: '5',
            b: '6'
        }],
    },
    components: {
        FoobarWidget,
    },
    computed: {
        myJson() {
            return JSON.stringify(this.foobars, null, 2);
        }
    },
    methods: {
        addNew() {
            this.foobars.push({
                a: '1',
                b: '2'
            });
        },
        foobarDead(index) {
            this.foobars.splice(index, 1);
        }
    }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>
<div id="app">
  <button @click="addNew()">add foobar</button>
  <h3>Foobars</h3>
  <ul>
    <foobar-widget v-for="(foobar, index) in foobars" :key="index" @die="foobarDead(index)" v-model="foobars[index]"/>
  </ul>
  <h3>JSON</h3>
  <pre>{{myJson}}</pre>
</div>

As one can see after trying this example, it mostly works - except it doesn't reflect the changes very well in parent component (generated JSON) after changing the values in child components (a, b, and computed sum).

The question seems similar to SO: Computed property on child component props, however OP of that question had a computed value doing string formatting, making use of Vue filters a good solution. This is not the case here - the sum() computed property can be an arbitrary function, which is needed to be accessed both in parent and in child components.

The way I did it above, by modifying the prop object by adding a sum property to it when it's recomputed is definitely not the right way to do it, hence my question. Not only it works wonkily, but it also produces an ESLint warning (the WARNING comment in code above).

like image 850
Zaroth Avatar asked Oct 16 '22 22:10

Zaroth


1 Answers

The reason your parent doesn't update is that you're adding a property to an object, which Vue doesn't detect.

Instead of

    sum() {
        const s = Number(this.value.a) + Number(this.value.b)
        // WARNING eslint - vue:no-side-effects-in-computed-properties
        this.value.sum = s;
        return s;
    }

You would do

sum() {
    const s = Number(this.value.a) + Number(this.value.b)
    this.$set(this.value, 'sum', s);
    return s;
}

Notwithstanding the code smell of side effects in a computed, and updating values in a prop, which suggests you ought to be doing something differently.

like image 140
Roy J Avatar answered Nov 15 '22 04:11

Roy J