How to call api before close App in Nuxtjs - vue.js

I would like to know which is the best way to call an API right before we closing browser, reload, refresh.
I want to save some data to firebase when user close app/ refresh page to know their behavior.
I have tried window.addEventListener("beforeunload", callback) and beforeDestroy lifeCycle but it seems this doesn't work as expected.
I have checked the function call api has no problem.
I guess the problem is calling api need time api return data while the action closing browser/refresh/reload will happen immediately. So the api call doesn't have enough time to execute.
Nuxt version 2.15.8
Vue version 2.7.10
<template>
<div>
<h1>Page Firebase</h1>
</div>
</template>
<script>
export default {
methods: {
async callBack() {
// call api here
},
},
mounted() {
window.addEventListener("beforeunload", this.callBack);
},
beforeDestroy() {
this.callBack();
},
};
</script>

Related

Vue JS best approach to wait for value in mounted hook

In my Vue component I want to fetch work orders via axios. To do that, I need a user id, which is loaded via a computed property.
Just calling my method 'loadWorkorders' in the mounted lifecycle hook, results in the computed property not being available yet. So I want to check if this computed property is available, if not, just wait a few milliseconds and try again. Repeat the same cycle until it is available.
What is the best approach to handle this? Now I'm just waiting for a second and this seems to work, but I'm looking for a more robust approach.
My code:
export default {
name: "MyWorkorders",
mounted() {
if (this.user) {
this.loadWorkorders();
} else {
setTimeout(this.loadWorkorders(), 1000);
}
},
data() {
return {
workOrders: null,
};
},
methods: {
loadWorkorders() {
axios
.get(`/workorders/load-user-workorders/${this.user.id}`)
.then((res) => {
this.workOrders = res.data.workorders;
})
.catch((err) => {
console.log(err);
});
},
},
computed: {
user() {
return this.$store.getters.getUser;
},
},
};
If you are using Vue 3, consider using Async Components, which are also related to the Suspense built-in component. (at the time of this post, Suspense is an experimental feature, however Async Components are stable)
If you are using Vue 2, take a look at this part of the documentation about handling loading state in Async Components.
If you do not want to invest in these solutions because your use case is very, very simple -- you can try the following simple solution:
<template>
<template v-if="loading">
loading...
</template>
<template v-else-if="loaded">
<!-- your content here -->
</template>
<template v-else-if="error">
error
</template>
</template>
I have made this runnable example for demonstration. Of course, the templates for loading and error states can be made more complex if required.

How does Vue lifecycle works? and use it for skeleton and loading

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.

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>

Nuxt async fetch() creating multiple instances? Repeated fetch() calls

I have a simple BasePreviewImage component that needs to fetch an Array.Buffer() asynchronously from an internal API. However, it appears that async fetch() is called for every instance ever created despite the components themselves being destroyed.
Example:
<template>
<div class="image-container">
</div>
</template>
<script lang="ts">
import { Vue, Component, Prop } from 'nuxt-property-decorator'
#Component({})
export default class BasePreviewImage extends Vue {
#Prop({ type: String }) id: string
#Prop({ type: String, default: 'small' }) size: string
image: string = ''
async fetch() {
console.log('async fetch', this)
}
created() {
console.log('created')
}
mounted() {
console.log('mounted')
}
beforeDestroy() {
console.log('beforeDestroy')
}
}
</script>
Output when I load a page with 1 BasePreviewImage component then back, then re-open the page. This continues calling fetch n times the page has been opened.
How do I avoid making the API call multiple times as a user navigates pages and is there some other memory leak going on here?
I'm not really sure if the problem is code, config, vue, nuxt, nuxt-property-decorator, vue-class-component, or somewhere else.
Related but not helpful: https://github.com/vuejs/vue-class-component/issues/418

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?