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

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?

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

Issues with data method & context within Vue

I am getting some strange behavior in my Vue application that I am developing.
In my view I define my data initially:
...
data() {
return {
organization: {
selectedOption: null,
options: [],
},
};
},
...
Intention is to populate this via a call to my backend API, which I do using => notation via axios:
// The following snippet is in my methods:
...
axios.get('http://localhost:8000/api/org/types')
.then((response) => {
Object.keys(response.data).forEach((k) => {
this.organization.options.push({
value: k,
text: response.data[k],
});
});
this.organization.selectedOption = this.organization.options[0].value;
});
...
The data comes in, and I can see it indeed does set the values until I go elsewhere within the view.
I initially called the method above in the beforeMount() method however I moved it to the created() method (due to data context/reactivity matters) and all seemed to be working just fine.
Now I am having an issue where when accessing the data where it is always seemingly set to initial data I have defined. I am verifying this via debug/console.
Within mounted():
console.log(this.organization); // Returns observer that shows the data I would expect to be there via Console, but initial data when accessing anything.
console.log(this.organization.selectedOption); // Returns null
Is there something I am not understanding how the Vue data method works? I was under the assumption that after the context has been created the underlying data can then be mutated for the life-cycle of that view.
EDIT:
I did attempt to return the promise on the axios call, but to no avail.
There are a couple of keys things to note here.
Firstly, when you log an object to the console it is live. You'll probably see a little blue 'i' icon after you expand the object that explains this. What this means is that the object properties are not copied. Instead the console just has a reference to the object. It only grabs the property values when you click on the object in the console to expand it. You can work around this by logging out console.log(JSON.stringify(this.organization)) instead.
The second point to note is that it really doesn't matter which hook you use to load the data. The hooks beforeCreate, created, beforeMount and mounted will all run synchronously at the relevant stages. Meanwhile, your data is being loaded asynchronously. Vue won't wait for it, there's no support for that. No matter which hook you use the data won't be loaded until after the initial rendering/mounting is complete. This is a common problem and you just need to write your component in such a way that it can cope with the data being missing when it first renders.
To be clear, I'm not saying that the hooks are interchangeable in general. They most definitely aren't. It's just that when you're loading data using an AJAX request it doesn't make any real difference which you use. The AJAX request will always come back after all of those hooks have been run. So performing the request in an earlier hook won't make the data available in the later hooks.
A common alternative is to load data in a parent component and only create the child once the data is loaded. That's usually implemented using a v-if and the data is then passed using a prop. In that scenario the child doesn't have to deal with the data being missing.

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>

Vue - Passing data between dynamic components using eventBus?

I have 3 Components in my 'Search' (parent) View; 'SearchForm', 'ResultList', 'DetailPage'. I switch from the SearchForm to the ResultList when I have received a response from the backend.
<keep-alive>
<component v-bind:is="currentComponent"></component>
</keep-alive>
When a response is recieved in my 'SearchForm' I save it to the searchBus;
searchBus.$emit('searchIssue', response.data);
Then, in my ResultList I want to retrieve it again and display the results;
mounted() {
searchBus.$on(['searchIssue'], (search) => {
this.table_items = search;
});
}
I display a loading animation (also a component) until the response is fully loaded and the ResultList is displayed.
Due to the Vue lifecycle everything is working when all components are displayed in one View, as they are already listening when the bus is updated.
Should I choose a different approach? E.g. using v-show or pass the response back to the Parent and inserting it again with a prop (Idk if it would work as not all components have the same props).
Is there a way to use the Bus anyway ? And how could it be solved making it one linear hierarchy and still hide the non-relevant components? (SearchForm -> ResultList -> DetailPage)
Should I choose a different approach?
I thing that is coming time for using Vuex
At the center of every Vuex application is the store. A "store" is
basically a container that holds your application state. There are two
things that make a Vuex store different from a plain global object:
Vuex stores are reactive. When Vue components retrieve state from it, they will reactively and efficiently update if the store's state
changes.
You cannot directly mutate the store's state. The only way to change a store's state is by explicitly committing mutations. This
ensures every state change leaves a track-able record, and enables
tooling that helps us better understand our applications.

what is the best way to use vue, vue-router to make a dashboard with keep-alive?

I'm using vue2, vue-router with keep-alive to develop a dashboard. What I want to achieve is following:
page should re-fetch when login -> logout -> login other account
(use beforeRouterEnter, beforeRouterLeave to re-fetch !? or use condition to keep alive component or not !?)
but vue-router can't use vue data to check page should keep alive or not...
in HTML template, it will render and render. If there has some vuex data show directly in HTML, it will error with not have the object inside object yet ex:
user: {
plan: {
xxx: ''
}
}
sure I can give vuex data all of property init data. It can avoid this thing happen, but if the page with keep alive, it still happens...
also, I can write many v-if in the template, but I think it's not a good idea to put v-if all around the template to make sure data exist..ex:
v-if="user.plan && user.plan.xxx"
or add v-if in component root template, is that a good idea !?
use beforeMount without keeping alive, there is no problem but it's not a good user experience dashboard (when you change page, data disappear or you have already load page data but load again)
Is there anyone who really use Vue 2 and it's relative library (vuex, vue-router) to develop can tell me how you develop your SPA project !?