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>
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.
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.
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.
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.
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
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With