I am trying to get all of the Vue components data properties from within a mixin that I am using for my plugin.
I have got the plugin registering correctly and the mixin is actively working.
I have added a lifecycle hook from within the Vue.mixin which runs on the created() event.
I am trying to loop through all of the components data and assign it to a property within my plugin, this is because I want to manipulate all of the data in every single component in my application.
I have tried to do the following:
Vue.mixin({
created() {
console.log(this.$options.data());
}
})
But i get the result saying that data is not a function. However, calling within the () on the end of the data logs a function out which I can see the data within.
Does anyone have any ideas?
Thank you
Try to use this.$data instead:
Vue.mixin({
created() {
console.log(this.$data);
}
});
>> DEMO on CodeSandbox
Related
I am quite new to Vue.js and while developing my first project i have stumbled upon a problem which I think is related to the component's lifecycle.
The context is the following:
I am using Vue 3 with composition API.
I have a "Map" component in which I use d3.js to show a chart.
In my Setup() method I have onBeforeMount() and onMounted().
In onBeforeMount() method, I want to use data from my Firestore database for the values in the chart. The console.log on line 47 shows the data correctly.
In onMounted(), is where I plan to put my d3 chart. But when I try to access the data as in console.log on line 55, I get undefined...
Ideally, from "Map" component, I want to fetch the data from my database and use it in order to create the chart and then have the component render the chart.
Then I would have another component called 'MapSettings" that will be able to alter the data in "Map" such as filters etc...and have the values automatically updated in the chart.
Finally, both components will be siblings and have same parent component. So "Map" and "MapSettings" are on same hierarchical level.
But first I need to understand why I am getting undefined.. Any help or suggestion would be greatly appreciated!
Your lifecycle hooks look nice. The problem that you're facing has to do with code been executed asynchronously and code been executed synchronously.
You're declaring a function that uses async-await feature. This function will be executed asynchronously. In this case, you're getting data from Firestore and storing it in a ref at onBeforeMount().
On the other hand, you're declaring a normal function at onMounted() trying to access a ref's value, which will result in undefined because the function that you define at onBeforeMount() didn't finish its execution (or it's in the event queue) when onMounted is called.
That's why you first see the console.log that comes from onMounted.
One solution would merge both functions in one lifecycle hooks and use async await:
setup() {
const {actorDocs, load} = getActorDocs()
const actorsData = red([])
// load actor data from db
onBeforeMount( async () => {
await load()
actorsData.value = actorDocs
console.log(actorsData.value)
// manipulate it here...
})
}
Keep in mind that you cannot pause with async-await a lifecycle hook. What you're pausing is the function that Vue will execute in that hook. Indeed, this is really nice because pausing a hook wouldn't be efficient at all.
i face that problem, in my case i want use imgsRef.value out of onBeforeMount scope. How to get imgsRef value out of beforeMount scope?
onBeforeMount( async () => {
await axios
.get("http://localhost:3000/ourMoment")
.then((response) => {
imgsRef.value = response.data
// imhsRef get the value from api
console.log(imgsRef.value.photo)
})
})
// i try to concole.log here but the value still empty
console.log(imgsRef.value)
I have a call in my created method which has an await.
I want to know that the results of that call are loaded so that i can conditionally show/hide things in the DOM.
Right now it looks like the DOM is being rendered before that method has completed. But I though that methods in created were called before the DOM rendered?
You're correct in assuming that the created hook runs before the component mounts. However, the lifecycle hooks are not waiting for async calls to complete. If you want to wait for that call to be completed and data to load, you can do so by using a Boolean that you set to true when your data has loaded.
Your template:
<div v-if='dataLoaded'>Now you can see me.</div>
in your vue instace
export default {
data () {
return {
dataLoaded: false
}
},
created () {
loadMyData().then(data => {
// do awesome things with data
this.dataLoaded = true
})
}
}
This way you can keep your content hidden until that call has resolved. Take care with the context when you handle the ajax response. You will want to keep this as a reference to the original vue instance, so that you can set your data correctly. Arrow functions work well for that.
Following this tutorial, I'm trying to programmatically create instances of a component on my page.
The main snippet is this:
import Button from 'Button.vue'
import Vue from 'vue'
var ComponentClass = Vue.extend(Button)
var instance = new ComponentClass()
instance.$mount()
this.$refs.container.appendChild(instance.$el)
However I get two errors:
The component I'm trying to instantiate contains references to the store, and these don't work: "TypeError: Cannot read property 'state' of undefined".
For the last line of the snippet (this.$refs.container.appendChild(instance.$el)) I get this error: "Uncaught TypeError: Cannot read property 'container' of undefined"
I'm really not sure how to troubleshoot this, if anyone strong in Vue.js could give me some hint as to why I'm getting these errors and to solve them that would be terrific.
1) Since you're manually instantiating that component and it doesn't belong to your main app's component tree, the store won't be automatically injected into it from your root component. You'll have to manually provide the store to the constructor when you instantiate the component ..
import ProjectRow from "./ProjectRow.vue";
import Vue from "vue";
import store from "../store";
let ProjectRowClass = Vue.extend(ProjectRow);
let ProjectRowInstance = new ProjectRowClass({ store });
2) In a Vue Single File Component (SFC), outside of the default export this doesn't refer to the Vue instance, so you don't have access to $refs or any other Vue instance property/method. To gain access to the Vue instance you'll need to move this line this.$refs.container.appendChild(instance.$el) somewhere inside the default export, for example in the mounted hook or inside one of your methods.
See this CodeSandbox for an example of how you may go about this.
This is another way to instantiate a component in Vue.js, you can use two different root elements.
// Instantiate you main app
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
//
// Then instantiate your component dynamically
//
// Create a component or import it.
const Hello = {
props: ['text'],
template: '<div class="hello">{{ text }}</div>',
};
// Create a componentClass by Vue.
const HelloCtor = Vue.extend(Hello);
// Use componentClass to instantiate your component.
const vm = new HelloCtor({
propsData: {
text: 'HI :)'
}
})
// then mount it to an element.
.$mount('#mount');
It works by assigning "this" to the property "parent". By setting the parent you also have access to the $store in the new instance. (Provided that "this" is another Vue instance/Component and already has access to the store, of course)
new (Vue.extend(YourNewComponent))({
parent: this,
propsData: {
whatever: 'some value',
},
}).$mount(el.querySelector('.some-id'))
If you don't need the reference to the parent, you can just leave "parent: this," out.
Important note: When mounting many (like 500+) items on the page this way you will get a huge performance hit. It is better to only give the new Component the necessary stuff via props instead of giving it the entire "this" object.
I went down this path, following all the examples above, and even this one: https://css-tricks.com/creating-vue-js-component-instances-programmatically/
While I got far, and it works (I made a lot of components this way), at least for my case, it came with drawbacks. For example I'm using Vuetify at the same time, and the dynamically added components didn't belong to the outer form, which meant that while local (per component) validation worked, the form didn't receive the overall status. Another thing that did not work was to disable the form. With more work, passing the form as parent property, some of that got working, but what about removing components. That didn't go well. While they were invisible, they were not really removed (memory leak).
So I changed to use render functions. It is actually much easier, well documented (both Vue 2 and Vue 3), and everything just works. I also had good help from this project: https://koumoul-dev.github.io/vuetify-jsonschema-form/latest/
Basically, to add a function dynamically, just implement the render() function instead of using a template. Works a bit like React. You can implement any logic in here to choose the tag, the options, everything. Just return that, and Vue will build the shadow-DOM and keep the real DOM up to date.
The methods in here seems to manipulate the DOM directly, which I'm glad I no longer have to do.
Is there a difference between the following? I've seen examples doing both and am unsure why you would choose one over the other.
Vue.component('test', {
data() {
return { myDataA: 10 };
}
//vs
created() {
this.myDataB = 10;
}
}
Variables set in created() on this will not be reactive. In order for them to be reactive, you must define them in the object returned by data().
Example (note how the text does not change in the output):
https://jsfiddle.net/oyf4quyL/
in a component, there are three places where you can define you data:
data properties
computed properties
props
the created property is lifecycle hook in Vue. what this means, is that the Vue will run this function when the component is created. there are also other lifecycle hooks in Vue you can use, like mounted or beforeMount or beforeCreate and etc.
now with this in mind, let's answer your question.
when you define myDataA in data property, Vue will automatically create some "watchers" for this data property, so anytime that you set a new value to myDataA, anywhere that is using it, will be called again. but when you define a property directly on Vue instance (this), you will lose this "watchers" feature. (which by the way is just some getters and setters!)
so as i said, the best way and the correct way to define a data property is on any of the three places that i mentioned, based on your need. (because each of them has different use-cases that the others).
I have a component whose data is initialized by ajax. I know vue.js has provide several lifecycle hooks: Lifecycle-Diagram. But for ajax to initialize the data, which hook(beforeCreate, create, mounted, etc) is the best place to do it:
hook_name: function() {
ajaxCall(function(data) {
me.data = data;
});
}
Currently, i do it in mounted, making it to re-render the component. But i think we should get the data before the first render. Can someone figure out the best way to do it?
If you want to initialize your component with data you receive from a request, created() would be the most appropriate hook to use but it is a request, it might not resolve by the end of created or even mounted() (when even your DOM is ready to show content!).
So do have your component initialized with empty data like:
data () {
return {
listOfItems: [],
someKindOfConfig: {},
orSomeSpecialValue: null
}
}
and assign the actual values when you receive them in your created hook as these empty data properties would be available at that point of time, like:
created () {
someAPICall()
.then(data => {
this.listOfItems = data.listOfItems
})
/**
* Notice the use of arrow functions, without those [this] would
* not have the context of the component.
*/
}
It seems like you aren't using (or aren't planning to use) vuex but I'd highly recommend you to use it for for managing your data in stores. If you use vuex you can have actions which can make these api calls and by using simple getters in your component you would have access to the values returned by the request.