Issues with data method & context within Vue - vue.js

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.

Related

Nuxt (SSR/Vuex): Dispatch an action once all of the components have been created

Consider the following Widget component.
<template>
<component :is="name" />
</template>
<script>
import { mapActions } from 'vuex';
export default {
props: {
name: {
type: String,
required: true
}
},
created() {
this.addWidget(this.name);
},
methods: {
...mapActions({
addWidget: 'widgets/add'
}) // adds widget name to an array
}
};
</script>
I want to have multiple components like this one all over the page.
I need to be able to gather all of the component names so I can fetch the relevant data for them.
My current idea is to add the component name to the store when the created hook fires.
That works just fine, but my problem is that I can't find a way to dispatch a Vuex action to fetch the data once all of the components have been created. I tried using various hooks to no avail since none of them seems to have Vuex access.
Am I missing something? Is there some kind of hook that fires after all of the components have been created (SSR) that has Vuex access. Or maybe there's some better way to achieve what I've been trying to do?
Why do you want to wait until all the widgets have been loaded to fetch the data? Instead, what I'd do is fetch the data for each Component as they get added in the page. Using this approach, each component would require a specific piece of data, and nothing better than to load each of them in a different request. Easier to test, easier to trace, adheres to the single responsibility principle.
If you are worried that two components may require the same data, then you can route all requests through an Object that hashes the request (endpoint + verb if using REST) and keeps a pool of active connections, so that if two requests come in one reuses the Promise of the other, and only one of them reaches the server.
Now, if you really want to do it using your original approach, make it so that each widget emits an event once it's been registered. The events are collected by the parent component (maybe the Page that loads all the widgets). The page knows which Widgets are being loaded. Every time an event is received it sets a flag on that specific widget to indicate it's been loaded. Once all widgets get a flag, the Page triggers a request to fetch the data.

Watching route in VueJS3 leads to an error

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.

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 and Vuex state checking

I am experimenting with Vue and VueX and while everything is working well, there is one aspect that is troubling with regards to the store mechanism.
I have a component that loads a set of data from a remote service via axios. It works correctly and is called when the component is created.
export default {
created() {
this.$store.dispatch('foo/getBar');
}
...
}
This correctly populates the "bar" variable in the component with the valeus returned from the api call.
When I next view the component in the application, the created function is called again and the api called again, which returns the same data.
What is the best practice way of avoiding subsequent calls until we know that there is different data to be collected? Or more precisely, how do I invalidate data in the store when necessary so that api call is made only when it needs it?
You can put your api call to the parent or root component then place a refresh button to the child component.
Or you can check if variable bar is empty then make the api call.

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