asyncData hook when hard refreshing in Nuxt - vue.js

I just realized that the asyncData hook is not called when hard refreshing the page. But I have important data to load to show on that page. And I want to make sure that they are always available, even when the user hard refreshes the page.

asyncData from the documentation
the promise returned by the asyncData hook is resolved during route transition
In that case, the best way is to use the fetch() hook and display a loader while you do finish your calls thanks to the $fetchState.pending helper.
Actually, I do think that fetch() is better in many ways.
Quick article on the subject: https://nuxtjs.org/blog/understanding-how-fetch-works-in-nuxt-2-12/
The one on the bottom of the page (Sergey's) is cool with some practical example of a nice implementation too.
You could also use this somewhat hacky solution in a layout to see if the initial location (the one you are when you hard refresh) is the one you want to be. Basically, if you land on a specific page (hard refresh or following a new window page for example) but want to have some custom behavior.
beforeCreate() {
if (!['some-other-page'].includes(this.$router.history._startLocation)) {
this.$router.replace({ name: 'index' }).catch(() => {})
}
},
Still, this one infinite loops if used in a middleware (or I made a mistake) and it's less clean than a fetch() hook.

Related

How to run a callback when vue router-link finished navigation

What I want to do is - the user clicks the router-link - this does not actually do a navigation but just refreshes the page with new data. This is no good for accessibility because the user does not know a navigation happened, but good for performance because full navigation not required.
So I would like some way to solve that problem.
The way I might naively expect to solve the problem is wait for navigation to be over and run a callback, or use a promise and when promise completes run code. The code running when navigation over would put the focus on some element at navigation finished.
I was hoping I could do something obvious like the following
<router-link :to="(router) => {
router.push('/').onComplete(() => {
code to set focus here
});
}"
but it doesn't look like that is possible.
How should I solve my problem, as close to this solution as possible please.
This sounds like you might be using the wrong tools for the job. If you aren't actually navigating, there's no good reason to use router link - if you purely want to have the aesthetics of a link, use <a>. And if you are just expecting data to be refreshed, don't use router.push but simply call the function you want by attaching a listener to the link. If you want to show some kind of loading animation during the data fetching, you could either just set a variable, or use a library like vue-wait
In your case this could be something like:
<a #click="onClick">Link to click</a>
...
data(){
return {
isLoading:false
}
}
methods:{
async onClick(){
this.isLoading=true
await fetch(...)
this.isLoading=false
}
}
To answer your original question as well - yes, it's possible to run code when a navigation is finished. There's quite a few ways to do it, but if you want to specifically run code after a router.push, you can do that - router.push is a promise. So router.push(...).then(...).catch() works as well

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).

How to avoid fetching data for page that has just been navigated away from when back button is pressed in vue spa

I have a vue spa app. I fetch data for the page in the created hook
My question is how can i avoid fetching data again for the page that has been previously navigated away from if back button is pressed
EDIT
my app is in vue 3 I later used < keep-alive > which worked but am unable to clear the cache even when using max prop on the < keep-alive :max="2" > component
Quite simply, by caching it.
There are several ways to implement some kind of caching, but it largely depends on your current setup.
If you are not using vue-router and the back button causes your page to re-render, you will need to persist the data some other way, such as by using localStore. If you're using vuex, and you want to persis the data, you can try vuex-persistedstate. I'm assuming though that you are not looking to persist the data and the page is not re-loading on redirect.
If you are already using vuex, you can cache the data in the store.
Another option is to use the keep-alive component
If you don't want to rely on a 3rd party tool, you can also wrap your API call with a caching function.
Here is what that may look like. When the data is loaded, it gets cached, and when the cache exists, the cached result is returned as a promise, so that the method returns the same type either way.
let getMyDataCache = null;
export const getMyData(){
if (getMyDataCache) return new Promise((resolve)=>resolve(getMyDataCache));
return fetch('http://example.com/movies.json')
.then(response => response.json())
.then(data => {
getMyDataCache = data;
return getMyDataCache;
});
}
You could use vuex to store the state for your page - then in your created() hook, check if the state has been populated before fetching the data.
To clarify (since this answer is getting downvoted and I'm not sure why): you need to store the fact that you have already fetched your data somewhere. You can't do it inside your component since it will be remounted when you return to the page. If you have a state management pattern in place (e.g. with Vuex), you can store your data there so that when the component is remounted, you can check your store to see if the data has already been fetched before you try to fetch it again.
If you are using vue-router, the answers on this thread might help you instead: How to make certain component "keep-alive" with router-view in Vue 3?

How to make next(false) in BeforeRouteEnter prevent Vue-Router from navigating to the route?

I am desperately to implement a navigation guard that will prevent users to access an item page if the item doesn't exist, based on the ID provided in route params.
It says in the Vue-Router guide that:
next(false): abort the current navigation. If the browser URL was changed(either manually by the user or via back button), it will be reset to that of the from route.
Yet in my component, using next(false) won't prevent the route change, or component rendering. It won't even navigate back as promised in the doc.
beforeRouteEnter(to, from, next) {
next(false)
ajaxcall$.subscribe(data => next(vm => vm.setData(data)))
I would have expected that obvious next(false) to work and prevent the component and route from rendering but nope. The ajax call is made and the data is set.
"Make sure that the next function is called exactly once in any given pass through the navigation guard." Vue Router - Global Before Guards
You have multiple next() calls.
Also, your ajaxcall$.subscribe() is most likely an async method, so by the time it calls next(vm => vm.setData(data)) the navigation has happened most probably.
You should use await to make sure the information from ajax is available before calling next().
It might not really be a good idea to slow down each page transition with an API call, but that's a different issue...

With VueJS, How can I intelligently retain Vuex state between components?

First, think of a table that has a search, sort, and pagination. Items (rows) have details, which loads a route to show the detail component. Within this detail component, there's another table with a search, sort, and pagination, that of which also has a detail view to drill down further.
I'm using vuex to retain the state of most components, at least where it makes sense. In this case, the search query, sort, and pagination are all stored in the store. Also keeping in mind, I'm trying to decouple my components as much as possible to make testing easier and provide better maintainability.
Imagine this routing structure:
/Properties Shows a list of property locations
/Properties/location/1 Shows a list of buildings within that location
/Properties/location/1/building/1 Shows a list of offices within that building
Let's say I'm on the first route, and I search for a location, that of which produces 3 records that match the search query. Great! I click on the first one, view some details... and click Go Back. My search is still intact as it was stored in vuex so my state is retained. Perfect.
I navigate away to another route, entirely separate from the locations section, and then navigate back. Yuck, my search is still there. This isn't what I would expect (intuitively), but would expect programmatically.
Here's how I solved it. Please tell me if there's a better way.
In my routes, I always use props, and in my main navigation component, I set it up to pass a clearState property.
So the component has this:
props: {
clearState: {
type: Boolean,
default() {
return false;
}
}
},
created() {
if (this.clearState) {
this.$store.dispatch("properties/setSearch", "");
}
this.$store.dispatch("fetchLocations");
}
And my route in the main navigation menu has this:
<router-link :to="{ name: "Locations", params: { clearState: true }}">Locations</router-link>
This works great. But, I have several layers of detail components as we drill down, so now I feel like I'm getting tightly coupled:
created() {
if (this.clearState) {
this.$store.dispatch("properties/setSearch", "");
this.$store.dispatch("buildings/setSearch", "");
this.$store.dispatch("offices/setSearch", "");
}
this.$store.dispatch("fetchLocations");
}
I played around with router guards, but access to this within the component isn't immediately available, which slows the request to my API. It also forced me to use meta properties to determine what to and from routes should force the clearing of state.
My Question
I've solved my issue, but I have a gut feeling there's a better design pattern out there that I haven't figured out yet.
Is my solution prone to unseen error?
Is there a better way to do this that I'm obviously missing?
I hope this makes sense.