Watching route in VueJS3 leads to an error - vue.js

I have this piece of code in my VueJS3 component:
watch(route, () => {
loadData();
findFilter();
});
loadData calls an API loading data, it works fine.
I put this watcher so that when a user calls the same component with another route, the data reloads. Indeed: I have filters, and my URLS are like this: example.com/thepage/optionalFilter.
optionalFilter is optional, I can even have no filter. If I don't use this watcher, the component doesn't reload the data corresponding to the new filter.
But when I use this watcher, I have a warning in the console in development mode:
Avoid app logic that relies on enumerating keys on a component instance. The keys will be empty in production mode to avoid performance overhead
And in production mode, the first time I have an error in the console, then if I refresh everything goes fine:
exception { name: "NS_ERROR_NOT_IMPLEMENTED"...
I think my problem comes from the fact I badly watch the route. Am I?
By the way, I tried to use key in my router-view, but it calls every function inside the onMounted block.

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>

How can I fire component specific events from within a class in a Vue component?

I am trying to integrate Fine Uploader in my project which is built on Laravel & Vue. This project has a lot of legacy code that I have to support (such as old CSS classes, etc.) so I need to integrate Fine Uploader as a JS plugin, and can't use any existing 3rd party Vue Fine Uploader components that may be out there.
I've managed to get it to work for the most part. However, here is the challenge that I present to you. Below, you can see a screenshot of my code, in which I am instantiating the Fine Uploader instance inside my mounted hook.
As you can see I have highlighted the part of the code where I would emit an event when a new file is submitted for uploading. Here however, since I am inside the Fine Uploader instance, when I do this.$emit, it doesn't work (as it shouldn't).
I tried to circumvent this by using a global eventBus. This worked, but created issues when several files are dropped into the uploader at once. The event fires multiple times and often loses track of which event was fired by which instance, thus causing the 'thumbnails component' to perform duplicate actions.
I am convinced that I need to fire 'component-specific' events so that the thumbnail components that are generated and updated (onProgress and onComplete) only take their relevant actions once.
How can I fire component specific events from within another class instantiation?
Thank you.
Your function callbacks don't have their contexts bound, so this inside the callback does not refer to the Vue instance (which would result in $emit being undefined).
There are a few different solutions:
Use an arrow-function:
onSubmitted: (id, name) => {
// `this` is Vue instance
}
Bind the function context with Function#bind:
onSubmitted: function(id, name) {
// `this` is Vue instance
}.bind(this)
Use a cached reference to the Vue instance:
const vm = this;
const f = new qq.FineUploaderBasic({
// ...
onSubmitted: function(id, name) {
vm.$emit(...)
}
})

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?