Reputation: 1709
I have a very simple page that contains two tabs, each tab contains a list of reports with different status.
The way I am trying to do is, I have two different data source array, one for each tab and third array that is to denote current displaying array items.
When user click on the tab, I replace the third array to either first or second array according to the tab clicked.
However, the v-for
section is not updated, but the v-show
section is updated. I spent quite a few hours to try to solve it, have tried Vue.set(...)
but still not working.
Anyone knows the detailed cause of the issue and what is the best solutions for it?
Following is the code, I use OnSenUI for Vue
and Vue router
, this is the complete component code:
<template id="main-page">
<v-ons-page>
<v-ons-toolbar>
<div class="center">Reports</div>
</v-ons-toolbar>
<v-ons-bar>
<p style="text-align: right; margin-right: 20px">
<v-ons-button @click="$ons.notification.alert('TODO: implement this.')">
+ New Report
</v-ons-button>
</p>
</v-ons-bar>
<v-ons-card v-for="report in showingReports" :key="report.requestNo">
<div class="title">
{{report.requestNo}}
</div>
<div class="content">
Submitted Date: {{report.submittedDate}}<br>
Status: {{report.status}}<br>
Subject: {{report.subject}}<br>
</div>
</v-ons-card>
<v-ons-card v-show="!showingReports || showingReports.length == 0">
<div class="content">
<p class="center">
No records found
</p>
</div>
</v-ons-card>
<v-ons-tabbar>
<v-ons-tab label="Open" @click="selectTab(0)" :active="selectedTab == 0"></v-ons-tab>
<v-ons-tab label="Closed" @click="selectTab(1)" :active="selectedTab == 1"></v-ons-tab>
</v-ons-tabbar>
</v-ons-page>
</template>
<script>
export default {
data() {
return {
showingReports: [],
openedReports: [],
closedReports: [],
selectedTab: 0
}
},
created() {
this.getReports();
},
methods: {
getReports() {
this.allReports = [
{
requestNo: "REPORT18070102",
submittedDate: Date(),
status: "OPENED",
subject: "Test1"
},
{
requestNo: "REPORT18070103",
submittedDate: Date(),
status: "OPENED",
subject: "Test2"
}
];
this.openedReports = this.allReports.filter(report => report.status == "OPENED");
this.closedReports = this.allReports.filter(report => report.status != "OPENED");
this.selectTab(0);
},
selectTab(tab) {
this.selectedTab = tab;
if(tab == 0){
this.showingReports = this.openedReports;
}else{
this.showingReports = this.closedReports;
}
},
}
}
To include more details, this is a simple starting project I started by reading some tutorials. This is the main.js:
import Vue from 'vue'
import App from './App'
import router from './router'
Vue.config.productionTip = false
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
This is the App.vue:
<template>
<div id="app">
<router-view/>
</div>
</template>
<script>
export default {
name: 'App'
}
</script>
This is the index.js under router foler. ReportList is the component file, which is located under components folder:
import Router from 'vue-router'
import ReportList from '@/components/ReportList'
import 'onsenui/css/onsenui.css';
import 'onsenui/css/onsen-css-components.css';
import Vue from 'vue';
import VueOnsen from 'vue-onsenui';
Vue.use(Router)
Vue.use(VueOnsen)
export default new Router({
routes: [
{
path: '/',
name: 'ReportList',
component: ReportList
}
]
})
The dependencies in package.json:
"scripts": {
"prod": "webpack-dev-server --inline --progress --config build/webpack.prod.conf.js",
"start": "npm run prod",
"e2e": "node test/e2e/runner.js",
"test": "npm run unit && npm run e2e",
"lint": "eslint --ext .js,.vue src test/unit test/e2e/specs",
"build": "node build/build.js"
},
"dependencies": {
"onsenui": "^2.10.4",
"vue": "^2.5.2",
"vue-onsenui": "^2.6.1",
"vue-router": "^3.0.1"
},
-----Removed the console log, it does not help---
Updated: I make it work through another way, render the whole list but use v-show to control whether to show it. I think the "v-for" is not very reactive compared to v-show.
<v-ons-card v-for="report in allReports" :key="report.requestNo" v-show="shouldShow(report)">
<div class="title">
{{report.requestNo}}
</div>
<div class="content">
Submitted Date: {{report.submittedDate}}<br>
Status: {{report.status}}<br>
Subject: {{report.subject}}<br>
</div>
</v-ons-card>
shouldShow(report){
if(this.selectedTab == 0){
return report.status == 'OPENED';
}else{
return report.status != 'OPENED';
}
},
selectTab(tab) {
this.selectedTab = tab;
var hasItem = false;
this.allReports.forEach(r => hasItem = hasItem || this.shouldShow(r));
this.emptyList = !hasItem;
console.log(this.emptyList);
}
Upvotes: 3
Views: 3470
Reputation: 17371
From List Rendering in the Vue docs:
When Vue is updating a list of elements rendered with
v-for
, by default it uses an “in-place patch” strategy. ...This default mode is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM state (e.g. form input values).
To give Vue a hint so that it can track each node’s identity, and thus reuse and reorder existing elements, you need to provide a unique
key
attribute for each item. An ideal value forkey
would be the unique id of each item. This special attribute is a rough equivalent totrack-by
in 1.x, but it works like an attribute, so you need to usev-bind
to bind it to dynamic values (using shorthand here)
Because of this, you should always add a unique key
attribute (with v-bind
) for every element in the list to let Vue know there was a change in state.
E.g., using the requestNo
attribute of each list item:
<v-ons-card v-for="report in showingReports" :key="report.requestNo">
Update (continuing my comment above):
You might want to consider using Vue's computed properties. They're quite powerful and intelligently update properties when their dependencies are updated. This might also fix your current issue with the list not rendering.
For example:
HTML
<v-ons-card v-for="report in computedReports" :key="report.requestNo">
<!-- ... -->
</v-ons-card>
JS
data: {
selectedTab: 0
},
computed: {
computedReports() {
return this.allReports.filter(report => this.isOpenedReports ?report.status != "OPENED" : report.status == "OPENED");
}
}
methods: {
getReports() {
this.allReports = [
{
requestNo: "REPORT18070102",
submittedDate: Date(),
status: "OPENED",
subject: "Test1"
},
{
requestNo: "REPORT18070103",
submittedDate: Date(),
status: "OPENED",
subject: "Test2"
}
];
this.selectTab(0);
},
selectTab(tab) {
this.selectedTab = tab;
},
}
Upvotes: 2