Is it possible to cancel all running JS functions when destroying a Vue component? - vue.js

I'm wondering if it's possible to cancel all running async functions belonging to a component when destroying that component. I have a View-component which fetches a lot of data when the user enters that page, the problem is if the user goes to another page before the data is fully loaded, then the request-queue-limit is reached and slows down the data-loading for the new page.

What I did to fix this was to just check the lifecycle-status of the component instance. I found out that it was possible to check this._isBeingDestroyed
and this._isDestroyed, and then do an early return.
async fetchDataFunction() {
if (this._isBeingDestroyed || this._isDestroyed) return;
// Code
}

Related

How to get access to template in asyncData?

I want to get access to this.$el in asyncData.
I use a database to store translations.
I want to get a list of translations that are used on the current page.
Then I will send a request to the server to receive them.
After that, I will merge it.
i18.mergeLocaleMessage( locale, message )
How to do it ?
You can access i18n with something like this, no need to access the template for this use case
asyncData ({ app }) {
console.log(app.i18n.t('Hello'))
}
Looking at the lifecycle of Nuxt, asyncData will happen before any template is generated at all, so it is impossible with asyncData.
And even if it was possible with some hacky trick, it would be a bit strange to have to look inside of your template to then have some logic for i18n fetching.
Why not getting a computed nested object and loop on this through your template, after you have fetched all of your required translations ?
Also, you're using asyncData + an API call each time ? So, for every page: you will stop the user, let him wait for the API call and then proceed ?
Latest point, if you are on your page and you hit F5, then asyncData hook will not be triggered. Just to let you know about this caveat.
Alternative solutions:
using the fetch() hook and display a loader until you have fetched all your translations, still better to not rely on the content of the template. This will work even on F5 and can produce a more smooth experience (by not blocking the navigation).
getting your i18n whole translations globally, at some point when your user's connection is idle. Rather than on per-page. Especially because you will need to handle the logic of not fetching some translations that you already have (if the user visits a page twice).

show loading spinner until all child components inside of a Page have been rendered - Nativescript

I'm trying to show an activity indicator, when I go from one page to another. The target page contains many components within it, and it takes time to load. that's why I need some way to listen when all the child components are loaded, and at that moment tell my variable isBussy to be false
<template>
<StackLayout>
<ActivityIndicator :busy="isBussy" v-if="isBussy" />
<StackLayout v-else>
<Component1 />
<Component2 />
<Component3 />
<Component4 />
</StackLayout>
<StackLayout>
</template>
<script>
import Component1 from '~/components/Component1'
import Component2 from '~/components/Component2'
import Component3 from '~/components/Component3'
import Component4 from '~/components/Component4'
export default {
data() {
return {
isBussy: true
}
},
mounted() {
this.$nextTick(function() {
// Code that will run only after the
// entire view has been re-rendered
this.isBussy = false
})
}
}
</script>
this code does not work, since once the navigation is indicated from the previous page with:
#tap="$goto('otherPage', { props: { foo: bar } })"
it remains stuck on the initial page, and all the components begin to load in the background of the destination page, but without displaying the parent page, changing to this, only when the whole process ends, and never show/hide the activity indicator as expected.
By the way this expected behavior works perfectly when i do request and process them with Promises, then I turn on or off a variable in the state and it works. but I can not replicate that behavior in the navigation between pages and listen to load all the components
EDIT
Finally I achieved the desired behavior with a little trick I found on the internet
mounted() {
setTimeout(() => {
this.isBussy = false
}, 500)
},
this causes that the rendering of all the children components is delayed only a little, so that the activity indicator is shown, but not too much to produce that none of the components contained in the else block is detected and begin to rendering
There are two main ideas to understand here I think. I'll describe both.
1. General technique to Fetch Data without blocking render
It sounds like you understand this concept at the parent component level but then are asking how to do something very similar for the child components that this page contains.
The way I handle this, is in my component, I have my data default to an isLoading state. Then, in beforeMount() or mounted(), I perform my asynchronous actions and make necessary changes to my page's data.
The problem becomes entirely recursive when we look at child components. You want to make sure your child components are rendering and that any long running data fetching that needs to occur within their implementation will simply cause them to re-render once that fetching is complete.
Here is a working example: https://codesandbox.io/embed/r4o56o3olp
This example uses Nuxt. Aside from the addition fetch() and asyncData() methods, the rest of the Vue lifecycle hooks are the same here.
I use new Promise and setTimeout to demonstrate an operation that would use promises and be asynchronous. (e.g. axios.get(..))
The About page loads, and the beforeMount() lifecycle hook performs the asynchronous fetching in a way that doesn't block the page from rendering.
I use the beforeMount() hook because, according to here ( https://alligator.io/vuejs/component-lifecycle/ ), it is the first lifecycle hook that we have access to once the page's data is reactive. (So modifying this.myDataProp would trigger a re-render if {{ myDataProp }} was used in the template).
I also included a child component where I purposely made its data take twice as long to load. Since I again, am letting the component render immediately, and then I handle the fetching/updating of data in an appropriate lifecycle hook, I can manage when the end-user perceives a page to be loaded.
In my working example, the LongLoadingComponent did the same exact technique as the About page.
Once you see how to use beforeMount() or mounted() to fetch data and then update state, I think the trick is to take a moment and really think about the default state of your component. When it first renders, what should the user see before any of it's data fetching/long-running operations are completed?
Once you determine what your default (not yet loaded) component should look like, try getting that to render on your screen, and secondarily add in the logic that fetches and updates state data.
2. Listening for when a Child Component is finished rendering from a parent component
This makes use of the above technique, but includes the usage of the updated() hook and emitting a custom event ( https://v2.vuejs.org/v2/guide/components-custom-events.html
)
If you really want to listen for when your child components are finished rendering, you can $emit a custom event in your updated() hook. Perhaps something like this (in one of your child components)
if (this.dataLoaded) { this.$emit('loadedAndRendered') }
So when the child's async operations are done, it can flip it's dataLoaded property to true. If dataLoaded is used in the child's <template> somewhere, then the component should re-render (for it's "finished" state). When the child re-renders, the updated() hook should trigger. (again, see: https://alligator.io/vuejs/component-lifecycle/ ) I included the if (this.dataLoaded) part just to handle case where updated() hook might be called during intermediate data updates. (We only want to emit loadedAndRendered event if child is finished loading data/updating.)
3. Other caveats about universal nuxt applications
It wasn't until after I wrote this answer that I realized you aren't using Nuxt. However I'm adding this in case other Nuxt users happen to come across this.
I'm adding this section just because it took some focused hands-on time for me to wrap my head around. A Nuxt Universal Application does both server-side and client-side rendering. Understanding when something renders on the client vs when it was rendered on the server was a little difficult for me at first. In the working example I linked above, when you visit the about page you can also see if that component was fetched from the server or if it was just rendered by the client.
I'd recommend playing with a Page's fetch() and asyncData() methods and see how it impacts when certain things render on your screen. ( https://nuxtjs.org/api/pages-fetch/ ) ( https://nuxtjs.org/api/ ). Seeing what these methods are useful for helps me also identify what they are not useful for.
If you're using a Vuex store, I'd recommend seeing what happens when you refresh a page or use instead of a to navigate between pages. (Seeing something like the SSR schema diagram can be helpful here: https://nuxtjs.org/guide#schema )
..I have yet to fully appreciate the details of the bundling and delivery behavior that Webpack provides for a Universal Nuxt app (See right side of diagram here: https://medium.freecodecamp.org/universal-application-code-structure-in-nuxt-js-4cd014cc0baa )

How To Ensure Reference Data Is Loaded In Vue?

I have webpack setup to bundle all of the source. I have a Vue object that is the page and multiple Vue components of my own creation. This works great!
I am now getting reference data from the database to fill in certain default options for some of these components. In my pages Mounted or Created events (there is no difference for my question) I am calling a method that will check to see if the data exists in localStorage and if not, it will extract the data from the database.
Once Extracted, I have it in localStorage so it is not an issue. However, the first time I need to gather the data (or when I need to refresh it because I have another trigger that lets me know when it has changed) the page and components have rendered (with errors because of lack of data) before the data comes back. The fetch method is in a promise, but mounted events don't seem to care if a promise exists within in before it continues to the next component.
So what is the best practice for loading/refreshing reference data in Vue? I am currently not using VueX because this is not a SPA. Sure, it is a single page that is doing things (there are many single pages that do their own thing in this site) but I have no need to make it a full SPA here. But If VueX and its store will give me some sort of guarantee that it will occur first or page/components will run AFTER VueX things, I will learn it.
Have you tried doing so:
<component v-if="page.isDataLoaded">...</component>
in your Vue-component:
data() {
return {
page: {
isDataLoaded: false,
}
}
},
mounted() {
this.fetchPageData().then(() => this.page.isDataLoaded = true);
}
You can use v-if and v-else to show, for example page loader element like so:
<PageLoader v-if="!page.isDataLoaded"></PageLoader>
<component v-else>...</component>

setTimeout inside component is getting called regardless of page

I have a setTimeout call that gets called every 5 minutes. The issue is that no matter what page I am on, the timeout is still getting called and is making api calls.
My setTimeout component is inside the home page, what would be ideal is killing just that component when no longer on the home page. I would like to avoid destroying the homepage component and not having to refetch data.
My setTimeout code is like this:
sendCords(){
this.props.actions.findCords()
this.beginTimeout()
}
beginTimeout(){
let timer = setTimeout(this.sendCords,300000);
this.setState({timer});
}
Note: Lifecycle hooks E.G. componentWillUnMount is not getting called, as the pages are like a stack - and the pages are not un-mounting. I am using react-native-router-flux
Any help would be much appreciated.
Well I ended up having to do a few things, in my actions I would check what page I was calling and dispatch an empty action if It was not called on the homepage. This would trigger a the clear timeout. Having a force refresh on my component was needed
<Comp refresh={() => this.setState({val:!this.state.val} />
And I would then I have a shouldComponentUpdate used like so when navigating back to the home page and needing to set the timeout again:
shouldComponentUpdate(nextProps, nextState){
if(this.props.removeTimer && !nextState.removeTimer){
this.timeOutFunc()
}

Vue.js async components: how to fire the render function more than once

I am using async components in a vue.js project, like this: https://v2.vuejs.org/v2/guide/components.html#Async-Components
Everything works correctly on the first load because my factory function fires. The factory function makes an ajax call, then returns the template to Vue. It also does some custom JS stuff.
My issue is that when a user wants to load that component again, the factory function does not fire again. It will only fire on the first load of the component.
For example, I have a component for each page on the website (Home, About, Contact, etc). When the user clicks on About for the first time, it's all good. But when they go to Contact, then back to About, Vue will not call the factory function, and instead inject what looks to be a cached version of the template.
I need my custom JS to fire again, because it's what triggers a lot of animations. Is it possible to fire the factory function multiple times, or do I need to think about restructuring, and if so, is there a a suggested way?