Nuxt loading progress bar loads multiple times - vue.js

I'm using default nuxt loading bar, it works well on simple pages. But when I use multiple axios requests progress bar loads every time request is sent. I want progress bar to understand all those request as a single page load. I used Promise.all and it kind of worked. But my problem is that I am using asynchronous vuex dispatch methods.
So my code is something like this, with three different asynchronous dispatch and progress bar loads three times. How can I make it so, that it loaded only once. Thanks
async fetch({ store }) {
await store.dispatch('LOAD_DATA_1')
await store.dispatch('LOAD_DATA_2')
await store.dispatch('LOAD_DATA_3')
}

It's loading three separate times because your requests are taking place sequentially, one after another, not all at once. To get around this, you can manually start/stop the loader.
First, you'll want to prevent the nuxt axios plugin from triggering the loading bar. See here.
this.$axios.$get('URL', { progress: false })
Then, you can manually start and stop the loading bar programatically before/after the requests are completed.
this.$nuxt.$loading.start()
this.$nuxt.$loading.stop()
Full example:
async fetch({ store }) {
this.$nuxt.$loading.start()
await store.dispatch('LOAD_DATA_1')
await store.dispatch('LOAD_DATA_2')
await store.dispatch('LOAD_DATA_3')
this.$nuxt.$loading.stop()
}
edit 1 (see comment):
To use in asyncData/fetch you can use the following. I'm not sure you should be accessing the components like this, but I don't see another way to access the $loading module within the context...
async fetch(ctx) {
// access the loading component via the access context
ctx.app.components.NuxtLoading.methods.start()
// example, wait 3 seconds before disabling loader
await new Promise(resolve => setTimeout(resolve, 3000))
ctx.app.components.NuxtLoading.methods.finish()
},

Related

Vue PWA caching routes in advance

I'm hoping someone can tell me if I'm barking up the wrong tree. I have built a basic web app using Vue CLI and included the PWA support. Everything seems to work fine, I get the install prompt etc.
What I want to do, is cache various pages (routes) that user hasn't visited before, but so that they can when offline.
The reason here is that I'm planning to build an app for an airline and part of that app will act as an in flight magazine, allowing users to read various articles, however the aircrafts do not have wifi so the users need to download the app in the boarding area and my goal is to then pre cache say the top 10 articles so they can read them during the flight.
Is this possible? and is PWA caching the right way to go about it? Has anyone does this sort of thing before?
Thanks in advance
To "convert" your website to an PWA, you just need few steps.
You need to know that the service worker is not running on the main thread and you cant access for example the DOM inside him.
First create an serviceworker.
For example, go to your root directory of your project and add a javascript file called serviceworker.js this will be your service worker.
Register the service worker.
To register the service worker, you will need to check if its even possible in this browser, and then register him:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/serviceworker.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope');
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
In vue.js you can put this inside mounted() or created() hook.
If you would run this code it will say that the service worker is successfully registered even if we havent wrote any code inside serviceworker.js
The fetch handler
Inside of serviceworker.js its good to create a variable for example CACHE_NAME. This will be the name of your cache where the cached content will be saved at.
var CACHE_NAME = "mycache_v1";
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open(CACHE_NAME).then(function(cache) {
return cache.match(event.request).then(function (response) {
return response || fetch(event.request).then(function(response) {
cache.put(event.request, response.clone());
return response;
});
});
})
);
});
Everytime you make a network request your request runs through the service worker fetch handler here first. You need to response with event.respondWith()
Next step is you first open your cache called mycache_v1 and take a look inside if there is a match with your request.
Remember: cache.match() wont get rejected if there is no match, it just returns undefined because of that there is a || operator at the return statement.
If there is a match available return the match out of the cache, if not then fetch() the event request.
In the fetch() you save the response inside the cache AND return the response to the user.
This is called cache-first approach because you first take a look inside the cache and in case there is no match you make a fallback to the network.
Actually you could go a step further by adding a catch() at your fetch like this:
return response || fetch(event.request).then(function(response) {
cache.put(event.request, response.clone());
return response;
})
.catch(err => {
return fetch("/offline.html")
});
In case there is nothing inside the cache AND you also have no network error you could response with a offline page.
You ask yourself maybe: "Ok, no cache available and no internet, how is the user supposed to see the offline page, it requires internet connection too to see it right?"
In case of that you can pre-cache some pages.
First you create a array with routes that you want to cache:
var PRE_CACHE = ["/offline.html"];
In our case its just the offline.html page. You are able to add css and js files aswell.
Now you need the install handler:
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
return cache.addAll(PRE_CACHE);
})
);
});
The install is just called 1x whenever a service worker gets registered.
This just means: Open your cache, add the routes inside the cache. Now if you register you SW your offline.html is pre-cached.
I suggest to read the "Web fundamentals" from the google guys: https://developers.google.com/web/fundamentals/instant-and-offline/offline-cookbook
There are other strategies like: network-first
To be honest i dont know exactly how the routing works with SPAs because SPA is just 1 index.html file that is shipped to the client and the routing is handled by javascript you will need to check it out witch is the best strategie for your app.

Nuxt JS - reuse async call (axios get) within mounted?

If I am rendering a page with Nuxt, Vue, and Axios - is there a way to reuse the asyncData request (or data)?. For example, if I render a response, and the user takes an action on the page to filter, sort, etc. the data, can I reuse the same data to render again - or do I need to make a new call in mounted?
export default {
asyncData ({ env, params, error }) {
return axios.get(`${env.cockpit.apiUrl}/collections/get/cat_ruleset?token=${env.cockpit.apiToken}&simple=1&sort[ruleid]=1`)
.then((res) => {
return { catrules: res.data }
})
.catch((e) => {
error({ statusCode: 404, message: 'Post not found' })
})
},
mounted() {
},
methods: {
}
}
Of course you can reuse it. The simplest way would be to store the result somewhere (anywhere, really, but your store would be a good storage candidate) and change your method to:
asyncData ({ env, params, error }) {
return X ? Promise.resolve(X) : axios.get(...)
}
... where X is the stored result of your previous call.
But you don't have to.
Because, by default, the browser will do it for you. Unless you specifically disable the caching for your call, the browser will assume making the same call to the server will yield the same result if you do it within the number of seconds set in max-age of Cache-control.
Basically, the browser returns the previous result from cache without making a call to the server, so the optimization you're after is already performed by the browser itself unless you specifically disable it.
You can easily spot which calls were served from cache and which from server by looking in the Network tab of DevTools in Chrome. The ones from cache will have (memory cache) in the Size column:
... and will have a value of 0ms in Time column.
If you want control over when to call the server and when to serve a cached result — most browsers have a limit on max-age (see link above) — you could (and should) store the result of your previous call and not rely at all on the browser cache (basically the internal check inside the method, which I suggested at the top).
This would enable you to avoid making a call long time after the cache max-age has passed, because you already have the data, should you choose to do so.

Load async data into the vuex store when Nuxt app loads

I am trialling a project in Nuxt. Liking it so far except I am a little confused as to how to load data from an external async service so that it is available in Vuex from the very first route.
I have tried adding middleware on the default layout to dispatch the store action but I do not see the service being called straight away. Only when I navigate deeper into the routes do I see the action dispatched.
I did something similar in a standard Vue project and added the created method to the App.vue.
Is there a similar way in Nuxt?
What you need is called a fetch.
The fetch method, if set, is called every time before loading the component (only for page components). It will be called server-side once (on the first request to the Nuxt app) and client-side when navigating to further routes.
Warning: You don't have access of the component instance through this inside fetch because it is called before initiating the component.
async fetch({ store }) {
await store.dispatch('your-action')
}
If you need parameter:
async fetch({ store, params }) {
await store.dispatch('your-action', params.id)
}
I gave an example of id. The name of the parameter depends on the name of your page.
_id => params.id
_slug => parmas.slug
...

Dispatch actions on authentication with Nuxt.js

I have an nuxt.js app that a user can log in to. Whenever the user is authenticated using the nuxt/auth module. I want the app to fetch some data using the nuxtServerInit.
Currently I have this in my nuxtServerInit:
export const actions = {
async nuxtServerInit({ dispatch }) {
if (this.$auth.loggedIn) {
await dispatch('products/getProducts')
...
}
}
}
This works well if I'm authenticated and the page is refreshed. The problem seems to be whenever I'm redirected after authentication, these actions are never called, thus not populating the store.
I have tried to use the fetch method instead, but in only works on the page with the fetch statement, not every page in my application. Also I don't want the http calls to be made with every page change.

Running api fetch on entering screen

I am creating an app in React Native (create react native) and I am attempting to fetch data from an API every time a user transitions to a screen.
For navigation, I am using the React Navigation library with a combination of Drawer and Stack Navigators.
Typically, I have seen fetch handled via the ComponentDidMount() lifecycle. However, when navigating to a screen in a stack navigator, the ComponentDidMount() lifecycle doesn't appear to trigger and the fetch doesn't run.
Ex. user is on post index, then navigates to add post screen, submits and is redirected to view screen (for that post), then clicks back to return to index. Returning to index does not trigger ComponentDidMount() and fetch isn't run again, so results are not updated.
Additionally, sometimes I need to access navigation params to alter a fetch request when navigation between screens.
I originally was attempting to determine screen transition (when navigation params were passed) via componentWillReceiveProps() method, however, this seemed a bit unreliable.
I did more reading and it sounds like I should be subscribing to listeners (via react navigation). I am having a hard time finding recent examples.
Current process (example)
On the desired screen I would subscribe to listener(s) in the componentDidMount() method:
async componentDidMount() {
this.subs = [this.props.navigation.addListener('willFocus', payload => this.setup(payload))];
}
Based of an example, it sounds like its good practice to remove all subs when unmounting the screen, so I also add:
componentWillUnmount() {
this.subs.forEach((sub) => {
sub.remove();
});
}
then I add a setup() callback method that calls whatever fetch methods I require:
setup = (payload) => {
this.getExampleDataFromApi();
};
Additionally, sometimes I need to access Navigation params that will be used in API queries.
I am setting these params via methods in other screens like so:
goToProfile = (id) => {
this.props.navigation.navigate('Profile', {
exampleParam: 'some value',
});
};
It seems like I cannot access the navigation prop via the provided getParam() method when within callback method from a navigation listener.
For example, this returns undefined.
setup = (payload) => {
console.log(navigation.getParam('exampleParam', []));
};
Instead I am having to do
componentDidFocus = (payload) => {
console.log(payload.action.params.exampleParam);
};
Question
I wanted to ask if my approach for handling fetch when navigating seems appropriate, and if not, what is a better way to tackle handling API requests when navigating between screens?
Thanks, I really appreciate the help!