I have a page where a ClientPortfolio (parent component) containing a list of Securities (child component) are loaded in a v-data-table list.
The issue I have is that ClientPortfolio is fully reloaded every time I click on a security in the list causing the entire list to be refreshed causing scroll and selected class to reset, as well as unncessary performance overhead. I have looked at the documentation of Vue and nothing seems to point out how to only refresh a child component when it has parameters, it looks like the parent component is being refreshed as the route is changing every time a security is selected, despite expecting that Vue would know that only sub (nested route) is changing hence need to only reload the child component
The closest answer I got was explained on https://github.com/vuejs/vue-router/issues/230 which does not explain in the code how to achieve this.
routes.js:
routes: [
{
path: '/client/:clientno/portfolios/:portfolioNo',
component: ClientPortfolios,
children: [
{ path: 'security/:securityNo', component: Security }
]
},
]
Router link in ClientPortfolios.vue:
<router-link tag="tr" style="cursor:pointer"
:to="`/client/${$route.params.clientno}/portfolios/${selectedPortfolioSequenceNo}/security/${props.item.SecurityNo}-${props.item.SequenceNo}`"
:key="props.item.SecurityNo+props.item.SequenceNo">
</router-link>
Router view (for Security component) in ClientPortfolios.vue:
<v-flex xs10 ml-2>
<v-layout>
<router-view :key="$route.fullPath"></router-view>
</v-layout>
</v-flex>
Any hint on how to prevent parent from getting reloaded is appreciated.
EDIT: Trying to get closer to the issue, I notice that the "Key" attr in ClientPortfolios changes (as shown in the Vue debug window above) whenever I change the Security, could that be the reason? Is there a way to assign a key to ClientPortfolios component although its not a child one? Or a way to not update its key when navigating to different securities?
UPDATE: Full code
ClientPortfolios.vue
<template>
<v-layout row fill-height>
<v-flex xs2>
<v-layout column class="ma-0 pa-0 elevation-1">
<v-flex>
<v-select v-model="selectedPortfolioSequenceNo" :items="clientPortfolios" box label="Portfolio"
item-text="SequenceNo" item-value="SequenceNo" v-on:change="changePortfolio">
</v-select>
</v-flex>
<v-data-table disable-initial-sort :items="securities" item-key="Id" hide-headers hide-actions
style="overflow-y: auto;display:block;height: calc(100vh - 135px);">
<template slot="items" slot-scope="props">
<router-link tag="tr" style="cursor:pointer"
:to="{ name: 'Security', params: { securityNo: props.item.SecurityNo+'-'+props.item.SequenceNo } }"
>
</router-link>
</template>
<template v-slot:no-data>
<v-flex class="text-xs-center">
No securities found
</v-flex>
</template>
</v-data-table>
</v-layout>
</v-flex>
<v-flex xs10 ml-2>
<v-layout>
<keep-alive>
<router-view></router-view>
</keep-alive>
</v-layout>
</v-flex>
</v-layout>
</template>
<script>
import Security from '@/components/Security'
export default {
components: {
security: Security
},
data () {
return {
portfoliosLoading: false,
selectedPortfolioSequenceNo: this.$route.params.portfolioNo,
selectedPortfolio: null,
securityNo: this.$route.params.securityNo
}
},
computed: {
clientPortfolios () {
return this.$store.state.ClientPortfolios
},
securities () {
if (this.clientPortfolios == null || this.clientPortfolios.length < 1) {
return []
}
let self = this
this.selectedPortfolio = global.jQuery.grep(this.clientPortfolios, function (portfolio, i) {
return portfolio.SequenceNo === self.selectedPortfolioSequenceNo
})[0]
return this.selectedPortfolio.Securities
}
},
mounted () {
this.getClientPortfolios()
},
activated () {
},
methods: {
changePortfolio () {
this.$router.push({
path: '/client/' + this.$route.params.clientno + '/portfolios/' + this.selectedPortfolioSequenceNo
})
},
getClientPortfolios: function () {
this.portfoliosLoading = true
let self = this
this.$store.dispatch('getClientPortfolios', {
clientNo: this.$route.params.clientno
}).then(function (serverResponse) {
self.portfoliosLoading = false
})
}
}
}
</script>
Security.vue
<template>
<v-flex>
<v-layout class="screen-header">
<v-flex class="screen-title">Security Details </v-flex>
</v-layout>
<v-divider></v-divider>
<v-layout align-center justify-space-between row class="contents-placeholder" mb-3 pa-2>
<v-layout column>
<v-flex class="form-group" id="security-portfolio-selector">
<label class="screen-label">Sequence</label>
<span class="screen-value">{{security.SequenceNo}}</span>
</v-flex>
<v-flex class="form-group">
<label class="screen-label">Security</label>
<span class="screen-value">{{security.SecurityNo}}-{{security.SequenceNo}}</span>
</v-flex>
<v-flex class="form-group">
<label class="screen-label">Status</label>
<span class="screen-value-code" v-if="security.Status !== ''">{{security.Status}}</span>
</v-flex>
</v-layout>
</v-layout>
</v-flex>
</template>
<script>
export default {
props: ['securityNo'],
data () {
return {
clientNo: this.$route.params.clientno,
securityDetailsLoading: false
}
},
computed: {
security () {
return this.$store.state.SecurityDetails
}
},
created () {
if (this.securityNo.length > 1) {
this.getSecurityDetails()
}
},
methods: {
getSecurityDetails: function () {
let self = this
this.securityDetailsLoading = true
this.$store.dispatch('getSecurityDetails', {
securityNo: this.securityNo,
clientNo: this.clientNo
}).then(function (serverResponse) {
self.securityDetailsLoading = false
})
}
}
}
</script>
router.js
const router = new Router({
mode: 'history',
routes: [
{
path: '/',
component: Dashboard
},
{
path: '/client/:clientno/details',
component: Client,
props: true
},
{
path: '/client/:clientno/portfolios/:portfolioNo',
component: ClientPortfolios,
name: 'ClientPortfolios',
children: [
{ path: 'security/:securityNo',
component: Security,
name: 'Security'
}
]
}
]
})
UPDATE:
Just to update this as it’s been a while, I finally got to find out what the problem is, which is what @matpie indicated elsewhere, I have found out that my App.vue is the culprit where there is a :key add to the very root of the application: <router-view :key="$route.fullPath" />
this was a template I used from somewhere but never had to look at as it was "working", after removing the key, all is working as it should, marking matpie answer accepted.
component' you need to define the props passed in the child component. you need to call getCurrentUser() when the parent component initialises.
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.
Preventing component reload is the default behavior in Vue.js. Vue's reactivity system automatically maps property dependencies and only performs the minimal amount of work to ensure the DOM is current.
By using a :key
attribute anywhere, you are telling Vue.js that this element or component should only match when the keys match. If the keys don't match, the old one will be destroyed and a new one created.
It looks like you're also pulling in route parameters on the data object (Security.vue
). Those will not update when the route parameters change, you should pull them in to a computed property so that they will always stay up-to-date.
export default {
computed: {
clientNo: (vm) => vm.$route.params.clientno,
}
}
That will ensure that clientNo
always matches what is found in the router, regardless of whether Vue decides to re-use this component instance. If you need to perform other side-effects when clientNo
changes, you can add a watcher:
vm.$watch("clientNo", (clientNo) => { /* ... */ })
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