How does Vue lifecycle works? and use it for skeleton and loading - vue.js

this question my look silly but i got stuck in this!
i'm using Vuetify skeleton and created a data isLoading like the codes bellow. on page refresh every thing is fine but on route change (go back and forward in my pages) it's not working.
in my codes, when page is refreshed btn becomes disabled and mycomponent show skeleton using the same isLoading in its file. but when i go forward and back to it, my btn is loaded, not disabled, mycomponent load after a time without showing skeleton!
what's the problem! i guest it's about using the lifecycle!
<template>
<div>
<v-btn :disabled="isLoading">Button</v-btn>
<mycomponent />
</div>
</template>
<script>
import mycomponent from '~/components/mycomponent'
export default {
components:{
'mycomponent': mycomponent
},
data(){
return{
isLoading: true
}
},
created(){
this.isLoading = true
},
mounted(){
this.isLoading = false
}
}
</script>
mycomponent:
<template>
<v-skeleton-loader
:loading="isLoading"
type="button"
width="100%"
>
<v-btn>Button</v-btn>
</v-skeleton-loader>
</template>
<script>
export default {
data(){
return{
isLoading: true
}
},
created(){
this.isLoading = true
},
mounted(){
this.isLoading = false
}
}
</script>
So The Problem: It only works when i land on page for first time or refresh browser. by going forward and back to this page neither disable button nor skeleton on component works.
Update
I'm on NuxtJs v2.13

The created hook is called when the vue instance is created and the mounted hook is called when the vue instance has been mounted in the DOM. These hooks are called when a component is routed to for the first time or when the page is refreshed. That explains why it only works when you land on the page for the first time or refresh the browser.
When a component has been mounted, pressing the back button on the browser won't call the created and mounted hook.
To solve your problem, you can watch the $route object, by doing
App.vue
watch: {
'$route' () {
// this will be called any time the route changes
this.isLoading = true // you can think of a way to make isLoading false
}
},
For more on lifecycle hooks, check out this article.

Related

How to listen for a page $emit in a nuxt layout?

Nuxt 2.15.6; I want to switch the layout of my menu component by dynamically switching menu components in my root layout.
default.vue
<template>
<component :is="navLayout"></component>
<Nuxt :navLayout="navLayout = $event" />
</template>
data() {
return {
navLayout: "default"
};
},
In the "child" components of , my pages eg. login.vue (/login) I $emit an event;
...
import nav2 from "#/layouts/nav2";
...
created() {
this.$emit("navLayout", nav2);
},
Now it seems to be the <Nuxt> component is not able to catch the event. I also tried calling a <Nuxt #navLayout="test()" /> method.
How can I avoid this.$root.$emit(...); in my login.vue and
this.$root.$on("navLayout", navLayout => {
this.navLayout = navLayout;
});
in default.vue?
EDIT: This answer works fine as it looks like you cannot do it right now: https://github.com/nuxt/nuxt.js/issues/8122#issuecomment-709443008
In the child component
<button #click="$nuxt.$emit('eventName', 'nice payload')">nice</button>
On the default layout
<script>
export default {
created() {
this.$nuxt.$on('eventName', ($event) => this.test($event))
},
methods: {
test(e) {
console.log('test ok >>', e)
},
},
}
</script>
Putting a listener on Nuxt itself does not work.
<Nuxt #navLayout="navLayout = $event" :navLayout="navLayout" />
I can see the event go and the listener plugged to <nuxt></nuxt> but it does not trigger any method with the listener...
PS: works for <nuxt-child></nuxt-child> at least.
Maybe I don't understand your question correctly, but it seems to me that you are trying to do yourself what layouts are meant to do. This would mean that you would create a layout with the menu component for login, a default layout etc. like this:
login.vue
<template>
<LoginMenu>
<Nuxt/>
</template>
default.vue
<template>
<DefaultMenu>
<Nuxt/>
</template>
On your page you would do:
export default {
layout: login
}
And then that would load the layout with the login menu component. On all other pages it would load the default menu.
More info here: https://nuxtjs.org/examples/layouts/

Show spinner (preloader/loading indicator) whenever page changes and hide when all assets are loaded in Vue Gridsome

I am using Gridsome (Vue static site generator with Vue Router) and I've created a preloader in index.html, its a simple div that covers everything. In index.html I also added this JS code to hide the preloader when everything loads
window.onload = function() {
document.getElementById('preloader').style.display = 'none';
};
This works only for the initial load, but when changing pages I am having trouble showing it and hiding it again.
I've tried to add this to my Layout component's beforeDestroy() hook to show the preloader again
beforeDestroy() {
this.preloader.style.display = 'block';
}
which shows it successfully when the route is changed, but then if I add the hiding logic in mounted() like this
mounted() {
this.preloader.style.display = 'none';
}
the preloader is never showed in the first place.
I was unable to find any resources about this kind of loading indicators, all I can find are one's for async calls like axios or fetch. I've created preloaders before in static HTML files, but never in SPAs. Can someone please push me in the right direction? Even googling keywords will help
you can use vuex with this case.
first, add your state src/main.js
import DefaultLayout from "~/layouts/Default.vue";
import Vuex from "vuex";
export default function(Vue, { appOptions }) {
Vue.component("Layout", DefaultLayout);
Vue.use(Vuex);
appOptions.store = new Vuex.Store({
state: {
loading: false,
},
mutations: {
on(state) {
state.loading = true;
},
off(state) {
state.loading = false;
},
},
});
}
second, add spinner to ./src/layouts/Default.vue
<template>
<div class="layout">
// add your spinner here or another
<div v-if="$store.state.loading">loading</div>
<slot />
</div>
</template>
finally, add commit code pages, templete, or components. like below.
<script>
export default {
created() {
// commit("on") first
this.$store.commit("on");
// commit("off") last, after fetch data or more.
this.$store.commit("off");
},
};
</script>

Call API automatically to fetch data with prop value when component is displayed in Vue.js

I have a page which displays a list of mutual funds. With each mutual fund, I have a button to display their NAV history. This button calls a component which has an embedded API call to fetch the NAV history. I pass the fund code for which the data is to be fetched as a prop to the component. However, I am not able to trigger the API call automatically when the prop is called.
this is my code as of now:
Parent component (main page):
<template>
<!-- some code -->
<a href="#" #click="fetchNavHistory(fund)">
<v-icon small>history</v-icon>
</a>
<!-- some more code -->
<NAVHistory
:show="showNavHistory"
:amfi_code="amfi_code"
ref="history"
#hide="showNavHistory=false"
/>
</template>
export default {
name: "FundList",
components: {
NAVHistory
},
data() {
return {
showNavHistory: false,
amfi_code: 0
}
},
methods:{
fetchNavHistory(fund){
this.amfi_code = fund.amfi_code
this.showNavHistory = true
var child = this.$refs.history
child.fetchNavHistory()
}
}
}
Child component (where NAV history is displayed):
<template>
<!-- some code -->
</template>
<script>
export default {
props: {
show: Boolean,
amfi_code: Number
},
data(){
return{
apiURL: process.env.VUE_APP_BASEURL,
navHistory: [],
}
},
methods: {
async fetchNavHistory(){
try{
const response = await fetch(this.apiURL + '/navhistory', {
method: 'POST',
body: JSON.stringify({"amfi_code": this.amfi_code}),
headers: {'content-type': 'application/json; charset=UTF-8'},
})
const data = await response.json()
console.log(data)
this.navHistory = data
} catch(error){
console.log(error)
}
}
}
}
</script>
At first I tried calling the fetchNavHistory() method on updated() event. But that kept calling the API non-stop when the component was displayed on the screen.
Then I tried adding a watch for the show prop. But that didn't work at all.
Finally, as a workaround, I called the API from the parent component itself. While that is working, it is calling the component with the previous value of the amfi_code, rather than the updated value. So the first time it gets called, the amfi_code is passed as 0.
Is there a way to safely trigger the API call when the component is displayed, i.e., the show prop is set to true?
You can try watch with deep:true option that way the watch will be triggered when a component will be mounted. Or you can call API on mounted hook and check show prop in it.
deep:true means a watch will look at if changes occur not only for a watched prop but additionally at all nested props.
immediate:true means that a watch will fire after a component is mounted (when a watched prop has initial value).

How to dynamically mount vue component with props

Scenario / context
I have an overview component which contains a table and an add button. The add button opens a modal component. When i fill in some text fields in the modal and click the save button, a callback (given as prop) is called so the parent component (the overview) is updated. The save button also triggers the model toggle function so the model closes.
So far works everything like expected but when i want to add a second entry, the modal is "pre-filled" with the data of the recently added item.
Its clear to me that this happens because the model component keeps mounted in the background (so its just hidden). I could solve this by "reset" the modals data when the toggle function is triggered but i think there should be a better way.
I have a similar issue when i want to fetch data in a modal. Currently i call the fetch function in the mounted hook of the modal. So in this case the fetch happens when the parent component mounts the modal. This does not make sense as it should only (and each time) fetch when the modal is opened.
I think the nicest way to solve this is to mount the modal component dynamically when i click the "add" (open modal) button but i can't find how i can achieve this. This also avoids that a lot of components are mounted in the background which are possibly not used.
Screenshot
Example code
Overview:
<template>
<div>
// mount of my modal component
<example-modal
:toggleConstant = modalToggleUuid
:submitHandler = submitHandler />
// The overview component HTML is here
</div>
</template>
<script>
export default {
data() {
return {
modalToggleUuid: someUuid,
someList: [],
}
},
mounted() {
},
methods: {
showModal: function() {
EventBus.$emit(this.modalToggleUuid);
},
submitHandler: function(item) {
this.someList.push(item);
}
}
}
</script>
Modal:
<template>
<div>
<input v-model="item.type">
<input v-model="item.name">
<input v-model="item.location">
</div>
</template>
<script>
export default {
data() {
return {
modalToggleUuid: someUuid,
item: {},
}
},
mounted() {
// in some cases i fetch something here. The data should be fetched each time the modal is opened
},
methods: {
showModal: function() {
EventBus.$emit(this.modalToggleUuid);
},
submitHandler: function(item) {
this.someList.push(item);
}
}
}
</script>
Question
What is the best practive to deal with the above described scenario?
Should i mount the modal component dynamically?
Do i mount the component correctly and should i reset the content all the time?
You are on the right way and in order to achieve what you want, you can approach this issue with v-if solution like this - then mounted() hook will run every time when you toggle modal and it also will not be present in DOM when you are not using it.
<template>
<div>
// mount of my modal component
<example-modal
v-if="isShowModal"
:toggleConstant="modalToggleUuid"
:submitHandler="submitHandler"
/>
// The overview component HTML is here
</div>
</template>
<script>
export default {
data() {
return {
isShowModal: false,
modalToggleUuid: someUuid,
someList: []
};
},
mounted() {},
methods: {
showModal: function() {
this.isShowModal = true;
},
submitHandler: function(item) {
this.someList.push(item);
this.isShowModal = false;
}
}
};
</script>

vue-router not updating component outside of router-view

I have my root Vue component setup like this:
<div>
<main-nav></main-nav>
<router-view></router-view>
</div>
So, main-nav is a component outside of the router-view. I'm having trouble with the main-nav updating itself properly when data changes or when navigating from route to route. Here's a simplified version of main-nav:
<template>
<li v-show="loggedIn">
<a #click="logout">Logout</a>
</li>
</template>
<script>
import { isLoggedIn, logout } from '#/tools/Auth'
export default {
computed: {
loggedIn: {
cache: false,
get() {
return isLoggedIn()
}
}
},
methods: {
logout() {
logout().then(() => {
this.$router.push({ name: 'Login' })
// this is necessary or the nav isn't updated after logout
this.$forceUpdate()
})
}
}
}
</script>
First I had to set cache: false on the loggedIn property or it wouldn't update at all after logging out. I also had to add a $forceUpdate() after logout in order for the component to refresh itself. Now I'm having the same problem after login - the component just doesn't refresh. I can see in the vue dev tools that loggedIn is true, but it won't show the logout link until I refresh the page.
It's not a race condition, there's nothing async going on at the moment. I'm just setting and deleting a cookie right now to get this working.
Anyone have an idea why I'm having these issues? I'm fairly familiar with vue-router from other projects and I've read the documentation but maybe I missed something?