Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Vue.js computed property not updating

I'm using a Vue.js computed property but am running into an issue: The computed method IS being called at the correct times, but the value returned by the computed method is being ignored!

My method

computed: {     filteredClasses() {         let classes = this.project.classes         const ret = classes && classes.map(klass => {             const klassRet = Object.assign({}, klass)             klassRet.methods = klass.methods.filter(meth => this.isFiltered(meth, klass))             return klassRet         })         console.log(JSON.stringify(ret))         return ret     } } 

The values printed out by the console.log statement are correct, but when I use filteredClasses in template, it just uses the first cached value and never updates the template. This is confirmed by Vue chrome devtools (filteredClasses never changes after the initial caching).

Could anyone give me some info as to why this is happening?

Project.vue

<template> <div>     <div class="card light-blue white-text">         <div class="card-content row">             <div class="col s4 input-field-white inline">                 <input type="text" v-model="filter.name" id="filter-name">                 <label for="filter-name">Name</label>             </div>             <div class="col s2 input-field-white inline">                 <input type="text" v-model="filter.status" id="filter-status">                 <label for="filter-status">Status (PASS or FAIL)</label>             </div>             <div class="col s2 input-field-white inline">                 <input type="text" v-model="filter.apkVersion" id="filter-apkVersion">                 <label for="filter-apkVersion">APK Version</label>             </div>             <div class="col s4 input-field-white inline">                 <input type="text" v-model="filter.executionStatus" id="filter-executionStatus">                 <label for="filter-executionStatus">Execution Status (RUNNING, QUEUED, or IDLE)</label>             </div>         </div>     </div>     <div v-for="(klass, classIndex) in filteredClasses">         <ClassView :klass-raw="klass"/>     </div> </div> </template>  <script> import ClassView from "./ClassView.vue"  export default {     name: "ProjectView",      props: {         projectId: {             type: String,             default() {                 return this.$route.params.id             }         }     },      data() {         return {             project: {},             filter: {                 name: "",                 status: "",                 apkVersion: "",                 executionStatus: ""             }         }     },      async created() {         // Get initial data         const res = await this.$lokka.query(`{             project(id: "${this.projectId}") {                 name                 classes {                     name                     methods {                         id                         name                         reports                         executionStatus                     }                 }             }         }`)          // Augment this data with latestReport and expanded         const reportPromises = []         const reportMeta     = []         for(let i = 0; i < res.project.classes.length; ++i) {            const klass = res.project.classes[i];            for(let j = 0; j < klass.methods.length; ++j) {                res.project.classes[i].methods[j].expanded = false                const meth = klass.methods[j]                if(meth.reports && meth.reports.length) {                    reportPromises.push(                        this.$lokka.query(`{                            report(id: "${meth.reports[meth.reports.length-1]}") {                                id                                status                                apkVersion                                steps {                                    status platform message time                                }                            }                        }`)                        .then(res => res.report)                     )                     reportMeta.push({                         classIndex: i,                         methodIndex: j                     })                 }             }         }          // Send all report requests in parallel         const reports = await Promise.all(reportPromises)          for(let i = 0; i < reports.length; ++i) {            const {classIndex, methodIndex} = reportMeta[i]            res.project.classes[classIndex]                       .methods[methodIndex]                       .latestReport = reports[i]        }         this.project = res.project         // Establish WebSocket connection and set up event handlers        this.registerExecutorSocket()    },     computed: {        filteredClasses() {            let classes = this.project.classes            const ret = classes && classes.map(klass => {                 const klassRet = Object.assign({}, klass)                 klassRet.methods = klass.methods.filter(meth => this.isFiltered(meth, klass))                 return klassRet             })             console.log(JSON.stringify(ret))             return ret         }     },      methods: {         isFiltered(method, klass) {             const nameFilter = this.testFilter(                 this.filter.name,                 klass.name + "." + method.name             )             const statusFilter = this.testFilter(                 this.filter.status,                 method.latestReport && method.latestReport.status            )            const apkVersionFilter = this.testFilter(                this.filter.apkVersion,                method.latestReport && method.latestReport.apkVersion            )            const executionStatusFilter = this.testFilter(                this.filter.executionStatus,                method.executionStatus            )            return nameFilter && statusFilter && apkVersionFilter && executionStatusFilter        },        testFilter(filter, item) {            item = item || ""            let outerRet = !filter ||            // Split on '&' operator            filter.toLowerCase().split("&").map(x => x.trim()).map(seg =>                // Split on '|' operator                seg.split("|").map(x => x.trim()).map(segment => {                    let quoted = false, postOp = x => x                    // Check for negation                    if(segment.indexOf("!") === 0) {                        if(segment.length > 1) {                            segment = segment.slice(1, segment.length)                            postOp = x => !x                        }                    }                    // Check for quoted                    if(segment.indexOf("'") === 0 || segment.indexOf("\"") === 0) {                        if(segment[segment.length-1] === segment[0]) {                            segment = segment.slice(1, segment.length-1)                            quoted = true                        }                    }                    if(!quoted || segment !== "") {                        //console.log(`Item: ${item}, Segment: ${segment}`)                        //console.log(`Result: ${item.toLowerCase().includes(segment)}`)                        //console.log(`Result': ${postOp(item.toLowerCase().includes(segment))}`)                    }                    let innerRet = quoted && segment === "" ?                        postOp(!item) :                        postOp(item.toLowerCase().includes(segment))                     //console.log(`InnerRet(${filter}, ${item}): ${innerRet}`)                     return innerRet                }).reduce((x, y) => x || y, false)            ).reduce((x, y) => x && y, true)             //console.log(`OuterRet(${filter}, ${item}): ${outerRet}`)            return outerRet        },        execute(methID, klassI, methI) {            this.project.classes[klassI].methods[methI].executionStatus = "QUEUED"            // Make HTTP request to execute method            this.$http.post("/api/Method/" + methID + "/Execute")            .then(response => {            }, error =>                console.log("Couldn't execute Test: " + JSON.stringify(error))            )        },        registerExecutorSocket() {            const socket = new WebSocket("ws://localhost:4567/api/Executor/")             socket.onmessage = msg => {                const {methodID, report, executionStatus} = JSON.parse(msg.data)                 for(let i = 0; i < this.project.classes.length; ++i) {                    const klass = this.project.classes[i]                    for(let j = 0; j < klass.methods.length; ++j) {                        const meth = klass.methods[j]                        if(meth.id === methodID) {                            if(report)                                this.project.classes[i].methods[j].latestReport = report                            if(executionStatus)                                this.project.classes[i].methods[j].executionStatus = executionStatus                            return                        }                    }                }            }        },        prettyName: function(name) {            const split = name.split(".")            return split[split.length-1]        }    },     components: {        "ClassView": ClassView    } } </script>  <style scoped> </style> 
like image 306
Jared Loomis Avatar asked Mar 08 '17 18:03

Jared Loomis


People also ask

What is the difference between watchers and computed property in VueJS?

Computed properties are used to calculate the value of a property based on some other conditions. Watchers, on the other hand, are not primarily used for changing the value of a property; instead, they are used to notify you when the value has changed and let you perform certain actions based on these changes.

What is difference between method and computed in VUE JS?

Methods accept arguments, while a computed property doesn't. That is why Vue JS methods are so versatile, you can even apply a method call within another method. Methods are defined often to display an output (such as the number of active users) by just clicking the button assigned to that function.

How do computed properties work in Vue?

In Vue. js, computed properties enable you to create a property that can be used to modify, manipulate, and display data within your components in a readable and efficient manner. You can use computed properties to calculate and display values based on a value or set of values in the data model.

How do I Rerender components in Vue?

The best way to force Vue to re-render a component is to set a :key on the component. When you need the component to be re-rendered, you just change the value of the key and Vue will re-render the component.


2 Answers

If your intention is for the computed property to update when project.classes.someSubProperty changes, that sub-property has to exist when the computed property is defined. Vue cannot detect property addition or deletion, only changes to existing properties.

This has bitten me when using a Vuex store with en empty state object. My subsequent changes to the state would not result in computed properties that depend on it being re-evaluated. Adding explicit keys with null values to the Veux state solved that problem.

I'm not sure whether explicit keys are feasible in your case but it might help explain why the computed property goes stale.

Vue reactiviy docs, for more info: https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats

like image 122
So You're A Waffle Man Avatar answered Oct 14 '22 16:10

So You're A Waffle Man


I've ran into similar issue before and solved it by using a regular method instead of computed property. Just move everything into a method and return your ret. Official docs.

like image 27
peaceman Avatar answered Oct 14 '22 17:10

peaceman