I have a strange problem. wanted to access some elements in the created() hook. Specifically:
this i have accesss to the refs object:
created() {
console.log(this.$refs)
}
// returns:
{
bar: div.red.bar
content: div.name-description
description: p.description.dark
redContainer: div.red.red-container
sections: div.sections
title: h1.name.dark
__proto__: Object
}
But when i try to target a specific element I end up with undefined:
created() {
console.log(this.$refs.content)
}
//returns
undefined
Does anybody know why I have this behavior?
Similar problems when trying to get the width / height from elements in computed properties...
(e.g. this.$refs.content.clientWidth)
You cannot access refs from the created hook because the child components/elements have not been instantiated yet; instead access from the mounted hook:
Vue.component('foo', {
template: '<div>foo</div>',
created() {
console.log('foo created')
}
})
new Vue({
el: '#app',
created() {
console.log('parent created: $refs.foo is null?', this.$refs.foo == null)
},
mounted() {
console.log('parent mounted: $refs.foo is null?', this.$refs.foo == null)
},
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<foo ref="foo"></foo>
</div>
The reason why you're getting a discrepancy between the console.log output showing that the components are there but when you access one of the components it's not is probably because the browser is evaluating the properties of this.$refs lazily only once you click the arrow to expand the properties of the object, and by the time it is evaluated the child components have been created.
this.$refs is an object type, when your output is created, it will get a value undefined, then it will get another value of object type in mounted, and both values have the same reference in memory, either changed, another will change.
Related
Can anyone please explain me what is happened in these codes and how can I solve it?
I get the data in the parent's mounted function and update its data. So I have the new object in the child. But the value of the property of this object is empty!
Parent:
<template>
<div class="main-page">
<main-content v-bind:config="mainContentConfig" />
</div>
</template>
mounted(){
fetchData().then(editions => { editions.sort((e1, e2) => e1.name.toLowerCase().localeCompare(e2.name.toLowerCase()))
this.mainContentConfig.intranetEditions = [...editions];
this.mainContentConfig.currentMenuIndex = 1;
});
}
Child:
mounted(){
console.log("AA==============>", this.config);
console.log("BB==============>", this.config.intranetEditions);
}
But on the console I have:
I found this problem when I fill other data in the child class with this.config.intranetEditions array which always is empty!
Edit:
I tried this code too, but no difference!
[...this.config.intranetEditions]
Edit 2 This code tested too, but nothing!
console.log("AA==============>", this.config);
console.log("BB==============>", JSON.stringify(this.config.intranetEditions));
The child-component is mounted but the parent fetch is not finished yet, so this.config is an observer until the fetch is done (so the then is fired) and the var fulfilled.
Can you try to watch the prop config in the child-component? then you will see when this.config is fulfilled.
https://v2.vuejs.org/v2/guide/computed.html#Watchers
UPDATE WITH EXAMPLE:
child-component
watch: {
config(newValue) {
console.log("AA==============>", newValue.intranetEditions);
checkConfigValue();
},
},
methods: {
checkConfigValue() {
console.log("BB==============>", this.config.intranetEditions);
};
},
So you can wether do something in the watcher with the newValue, or trigger a method and use this.config. Both consoles, will print the same in this case.
I'm trying to render a svg file containing Vue.js template syntax at specific places. The file gets rendered correctly.
Upon instantiation of the inner element (the svg), the template syntax gets replaced, but a vue warning is emitted:
vue.js:597 [Vue warn]: Property or method "data" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property. See: https://v2.vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.
In the SVG there is a text containing the template syntax {{data}}
Vue.component('svg-show', {
props: {
model: {
required: true
},
compiled:null
},
render: function (h) {
return h("div",
[
h(this.compiled, {
props: {
data: this.model
}
})
]);
},
created() {
},
mounted() {
console.log("mounted");
console.log(this.model.SVG);
this.compiled = Vue.compile("<div>" + model.SVG + "</div>");
}
});
https://jsfiddle.net/dg2hkeby/10/
i also tried to use a static variable for the data property, but this did not succeed either.
How would one achieve what i'm trying to do using Vue.js?
I'm using vue-meta to dynamically change my meta tags. I want to change it only on some particular pages.
I'm using metaInfo function and try to change, for example, a title. But data from my getter is undefined which is why I cannot change the title in meta tags. It seems like metaInfo function try to access data before the component actually has it.
Here is my code in the component:
<template>
...
</template>
<script>
export default {
metaInfo() {
return {
title: this.getViewPage.data.meta.title, // data is undefined
};
},
created() {
this.loadViewPage();
},
computed: {
...mapGetters(['getViewPage']),
},
methods: {
...mapActions(['loadViewPage']),
};
</script>
vue-meta just creates computed property from your metaInfo function (according to plugin source code), so I assume that your loadViewPage action fills data object asynchronously and your problem just transforms to null-checking problem.
So you should check data before using its properties, and when data will be loaded metaInfo will update object as well:
metaInfo() {
// don't know your return object structure,
// maybe you should check whole this.getViewPage
let data = this.getViewPage.data;
return {
title: data ? data.meta.title : "some placeholder title",
}
};
I am building a Vue 2 Webpack application that uses Vuex. I am trying to update a component's local state by watching a computed property which is getting data from the Vuex store. This is what the inside of the <script></script> section of my component looks like:
export default {
name: 'MyComponent',
data() {
return {
// UI
modal: {
classes: {
'modal__show-modal': false,
},
tags: [],
},
};
},
computed: {
tagList() {
return this.$store.getters.tagList;
},
},
watch: {
tagList: (updatedList) => {
this.modal.tags = updatedList;
},
},
};
As you can see, I have a computed property called tagList which fetches data from the store. I have a watcher that watches tagList so that whenever the store's data changes, I can update modal.tags to the new value.
As per Vue documentation, I can call this.propertyName and update my local component state but when I call this.modal.tags = updatedList;, I get the following error:
[Vue warn]: Error in callback for watcher "tagList": "TypeError: Cannot set property 'tags' of undefined"
Why does this error occur even though it looks no different than what is in Vue.js's documentation?
Don't use arrow functions.
Change from:
watch: {
tagList: (updatedList) => {
this.modal.tags = updatedList;
},
},
To:
watch: {
tagList(updatedList) { // changed this line
this.modal.tags = updatedList;
},
},
Vue docs mention this a few times:
Don't use arrow
functions
on an options property or callback, such as created: () => console.log(this.a) or vm.$watch('a', newValue => this.myMethod()).
Since arrow functions are bound to the parent context, this will not
be the Vue instance as you'd expect, often resulting in errors such as
Uncaught TypeError: Cannot read property of undefined
or
Uncaught TypeError: this.myMethod is not a function
It is basically a context/scope issue. When using arrow functions, the this does not refer to the Vue instance, but the enclosing context of where the component was declared (probably window).
That is because of the scope issue. You are calling this. from another context. So, within arrow functions, you don't have access to vuejs data.
I suggest you change the watch to:
tagList (updatedList) {
this.modal.tags = updatedList;
},
I ran into a problem the other day, and asked the great stack community for a solution.
The problem:
I nested the same module in other modules but I was defining state in this way:
state: {
// some state here
}
What was happening is all of my modules despite seeming being nested under disparate modules all shared the same state.
The solution
state() {
return {
// state here instead
}
}
The solution is to have a function return state rather than define it as an object literal. The why somewhat makes sense. Here are my questions
New questions
What is happening under the hood of the store when state is defined as an object literal versus a function returning an object literal?
Why would you ever not use the function version? It seems like easily the default choice, but even in vuex docs for modules, they opt to show state as an object literal.
tl;dr The reason for using a function is Module Reuse.
What is happening under the hood of the store when state is defined as an object literal versus a function returning an object literal?
For that, better check under the hood:
var Store = function Store (options) {
// ...
var state = options.state; if ( state === void 0 ) state = {};
if (typeof state === 'function') {
state = state() || {};
}
The code above, as you can see, checks if a state was provided. If not, it assigns an empty object ({}) as initial state.
Next it checks if state was a function. If it was, it executes it and assigns to state what it returned. If it returned undefined (or any falsy value) it, again, assigns to state the empty object {}.
So that's the difference between providing state as an object or function: if one was provided, it is executed. If an object is provided, it is assigned directly.
Why would you ever not use the function version? It seems like easily the default choice, but even in vuex docs for modules, they opt to show state as an object literal.
In general, yes, the object version may be more common, because you typically only declare the store object (and its state) once and just use it in your Vue instance.
A use case for the state function, otoh, is Module Reuse:
Module Reuse
Sometimes we may need to create multiple instances of a module, for
example:
Creating multiple stores that use the same module (e.g. To avoid
stateful singletons in the SSR when the runInNewContext option is
false or 'once');
Register the same module multiple times in the same
store.
Another possible case would be if you declared a Vuex module just once and attempted to use it more than one time under different namespaces.
As the examples above are similar, here's a demo (of the module case) to illustrate the problem:
const personModule = {
namespaced: true,
state: {name: "name"},
mutations: {
changeName(state, data) { state.name = data }
}
}
const myStore = new Vuex.Store({
strict: true,
modules: {
aliceNamespace: personModule,
bobNamespcace: personModule
}
});
new Vue({
store: myStore,
el: '#app',
mounted() {
this.changeAlicesName("Alice");
this.changeBobsName("Bob");
},
computed: {
...Vuex.mapState('aliceNamespace', {alicesName: 'name'}),
...Vuex.mapState('bobNamespcace', {bobsName: 'name'})
},
methods: {
...Vuex.mapMutations('aliceNamespace', {changeAlicesName: 'changeName'}),
...Vuex.mapMutations('bobNamespcace', {changeBobsName: 'changeName'})
}
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
<p>Alice's name: {{ alicesName }}</p>
<hr>
<p>Bob's name: {{ bobsName }}</p>
<hr>
<button #click="changeAlicesName('Eve')">Change Alice's Name</button>
</div>
As you can see, when we use the state, the same object is assigned as state of both modules. The effect of this is when we edit a module, the other is affected. In reality, they may be two different modules, but their state is just one same object.
In the example below, on the other hand, when we declare state as a function, we can reuse the module declaration freely, as many times as we want:
const personModule = {
namespaced: true,
state() { // changed to a function
return {name: "name"} // changed to a function
}, // changed to a function
mutations: {
changeName(state, data) { state.name = data }
}
}
const myStore = new Vuex.Store({
strict: true,
modules: {
aliceNamespace: personModule,
bobNamespcace: personModule
}
});
new Vue({
store: myStore,
el: '#app',
mounted() {
this.changeAlicesName("Alice");
this.changeBobsName("Bob");
},
computed: {
...Vuex.mapState('aliceNamespace', {alicesName: 'name'}),
...Vuex.mapState('bobNamespcace', {bobsName: 'name'})
},
methods: {
...Vuex.mapMutations('aliceNamespace', {changeAlicesName: 'changeName'}),
...Vuex.mapMutations('bobNamespcace', {changeBobsName: 'changeName'})
}
})
<script src="https://unpkg.com/vue"></script>
<script src="https://unpkg.com/vuex"></script>
<div id="app">
<p>Alice's name: {{ alicesName }}</p>
<hr>
<p>Bob's name: {{ bobsName }}</p>
<hr>
<button #click="changeAlicesName('Eve')">Change Alice's Name</button>
</div>
Because state is a function, it will generate a different state instance for each module, working as expected all the way.