How to call a method after Vuex data is available? - vue.js

I would like to call a method once, as soon as possible after its component loads, but it needs to be after a computed property that gets data from Vuex is defined.
For example:
computed: {
my_data: function() {
return this.$store.state.my_data;
}
},
methods: {
useData: function(){
axios.post('api/fetch', {'data': this.my_data});
}
},
mounted() {
this.useData(); //error: this.my_data is undefined;
},
watch: {
my_data: function(){
this.useData(); //never triggers
}
}
If I call this.useData() from mounted, my_data is still undefined. I tried setting a watcher on my_data, but it never triggers. I feel like I'm missing something obvious here.

Make sure the data in my_data is updating correctly in store. If still have issue, then use deep to watch my_data
watch:{
my_data:{
handler:function(){
this.userData();
},
deep:true
}
}
If you're using watch to trigger method, don't need to use to call it from the mounted.

It turns out the "undefined" error was caused by another object that shared a key name with my stored object. Unfortunately, the nebulous error message sent me on a wild goose chase after I assumed the stored object was the issue based on my inexperience with Vuex.

Related

Vuetify Centralize Rules [duplicate]

The following code has been written to handle an event after a button click
var MainTable = Vue.extend({
template: "<ul>" +
"<li v-for='(set,index) in settings'>" +
"{{index}}) " +
"{{set.title}}" +
"<button #click='changeSetting(index)'> Info </button>" +
"</li>" +
"</ul>",
data: function() {
return data;
}
});
Vue.component("main-table", MainTable);
data.settingsSelected = {};
var app = new Vue({
el: "#settings",
data: data,
methods: {
changeSetting: function(index) {
data.settingsSelected = data.settings[index];
}
}
});
But the following error occurred:
[Vue warn]: Property or method "changeSetting" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in <MainTable>)
Problem
[Vue warn]: Property or method "changeSetting" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option. (found in <MainTable>)
The error is occurring because the changeSetting method is being referenced in the MainTable component here:
"<button #click='changeSetting(index)'> Info </button>" +
However the changeSetting method is not defined in the MainTable component. It is being defined in the root component here:
var app = new Vue({
el: "#settings",
data: data,
methods: {
changeSetting: function(index) {
data.settingsSelected = data.settings[index];
}
}
});
What needs to be remembered is that properties and methods can only be referenced in the scope where they are defined.
Everything in the parent template is compiled in parent scope; everything in the child template is compiled in child scope.
You can read more about component compilation scope in Vue's documentation.
What can I do about it?
So far there has been a lot of talk about defining things in the correct scope so the fix is just to move the changeSetting definition into the MainTable component?
It seems that simple but here's what I recommend.
You'd probably want your MainTable component to be a dumb/presentational component. (Here is something to read if you don't know what it is but a tl;dr is that the component is just responsible for rendering something – no logic). The smart/container element is responsible for the logic – in the example given in your question the root component would be the smart/container component. With this architecture you can use Vue's parent-child communication methods for the components to interact. You pass down the data for MainTable via props and emit user actions from MainTable to its parent via events. It might look something like this:
Vue.component('main-table', {
template: "<ul>" +
"<li v-for='(set, index) in settings'>" +
"{{index}}) " +
"{{set.title}}" +
"<button #click='changeSetting(index)'> Info </button>" +
"</li>" +
"</ul>",
props: ['settings'],
methods: {
changeSetting(value) {
this.$emit('change', value);
},
},
});
var app = new Vue({
el: '#settings',
template: '<main-table :settings="data.settings" #change="changeSetting"></main-table>',
data: data,
methods: {
changeSetting(value) {
// Handle changeSetting
},
},
}),
The above should be enough to give you a good idea of what to do and kickstart resolving your issue.
Should anybody land with the same silly problem I had, make sure your component has the 'data' property spelled correctly. (eg. data, and not date)
<template>
<span>{{name}}</span>
</template>
<script>
export default {
name: "MyComponent",
data() {
return {
name: ""
};
}
</script>
In my case the reason was, I only forgot the closing
</script>
tag.
But that caused the same error message.
If you're experiencing this problem, check to make sure you don't have
methods: {
...
}
or
computed: {
...
}
declared twice
It's probably caused by spelling error
I got a typo at script closing tag
</sscript>
Remember to return the property
Another reason of seeing the Property "search" was accessed during render but is not defined on instance is when you forget to return the variable in the setup(){} function
So remember to add the return statement at the end:
export default {
setup(){
const search = ref('')
//Whatever code
return {search}
}
}
Note: I'm using the Composition API
Adding my bit as well, should anybody struggle like me, notice that methods is a case-sensitive word:
<template>
<span>{{name}}</span>
</template>
<script>
export default {
name: "MyComponent",
Methods: {
name() {return '';}
}
</script>
'Methods' should be 'methods'
If you use two times vue instance. Then it will give you this error. For example in app.js and your own script tag in view file. Just use one time
const app = new Vue({
el: '#app',
});
I got this error when I tried assigning a component property to a state property during instantiation
export default {
props: ['value1'],
data() {
return {
value2: this.value1 // throws the error
}
},
created(){
this.value2 = this.value1 // safe
}
}
My issue was I was placing the methods inside my data object. just format it like this and it'll work nicely.
<script>
module.exports = {
data: () => {
return {
name: ""
}
},
methods: {
myFunc() {
// code
}
}
}
</script>
In my case, I wrote it as "method" instead of "methods". So stupid. Wasted around 1 hour.
Some common cases of this error
Make sure your component has the data property spelled correctly
Make sure your template is bot defined within another component’s template.
Make sure you defined the variable inside data object
Make sure your router name in string
Get some more sollution
It is most likely a spelling error of reserved vuejs variables. I got here because I misspelled computed: and vuejs would not recognize my computed property variables. So if you have an error like this, check your spelling first!
I had two methods: in the <script>, goes to show, that you can spend hours looking for something that was such a simple mistake.
if you have any props or imported variables (from external .js file) make sure to set them properly using created like this;
make sure to init those vars:
import { var1, var2} from './constants'
//or
export default {
data(){
return {
var1: 0,
var2: 0,
var3: 0,
},
},
props: ['var3'],
created(){
this.var1 = var1;
this.var2 = var2;
this.var3 = var3;
}
In my case it was a property that gave me the error, the correct writing and still gave me the error in the console. I searched so much and nothing worked for me, until I gave him Ctrl + F5 and Voilá! error was removed. :'v
Look twice the warning : Property _____ was accessed during render but is not defined on instance.
So you have to define it ... in the data function for example which commonly instantiate variables in a Vuejs app. and, it was my case and that way the problem has been fixed.
That's all folk's !
In my case, I forgot to add the return keyword:
computed: {
image(){
this.productVariants[this.selectedVariant].image;
},
inStock(){
this.productVariants[this.selectedVariant].quantity;
}
}
Change to:
computed: {
image(){
return this.productVariants[this.selectedVariant].image;
},
inStock(){
return this.productVariants[this.selectedVariant].quantity;
}
}
In my case due to router name not in string:
:to="{name: route-name, params: {id:data.id}}"
change to router name in string:
:to="{name: 'router-name', params: {id:data.id}}"
In my case I was trying to pass a hard coded text value to another component with:
ChildComponent(:displayMode="formMode")
when it should be:
ChildComponent(:displayMode="'formMode'")
note the single quotes to indicate text instead of calling a local var inside the component.
If you're using the Vue3 <script setup> style, make sure you've actually specified setup in the opening script tag:
<script setup>
I had lapsed into old habits and only created a block with <script>, but it took a while to notice it.
https://v3.vuejs.org/api/sfc-script-setup.html
Although some answers here maybe great, none helped my case (which is very similar to OP's error message).
This error needed fixing because even though my components rendered with their data (pulled from API), when deployed to firebase hosting, it did not render some of my components (the components that rely on data).
To fix it (and given you followed the suggestions in the accepted answer), in the Parent component (the ones pulling data and passing to child component), I did:
// pulled data in this life cycle hook, saving it to my store
created() {
FetchData.getProfile()
.then(myProfile => {
const mp = myProfile.data;
console.log(mp)
this.$store.dispatch('dispatchMyProfile', mp)
this.propsToPass = mp;
})
.catch(error => {
console.log('There was an error:', error.response)
})
}
// called my store here
computed: {
menu() {
return this.$store.state['myProfile'].profile
}
},
// then in my template, I pass this "menu" method in child component
<LeftPanel :data="menu" />
This cleared that error away. I deployed it again to firebase hosting, and voila!
Hope this bit helps you.
It seems there are many scenarios that can trigger this error. Here's another one which I just resolved.
I had the variable actionRequiredCount declared in the data section, but I failed to capitalize the C in Count when passing the variable as a params to a component.
Here the variable is correct:
data: () => {
return{
actionRequiredCount: ''
}
}
In my template it was incorrect (notd the no caps c in "count"):
<MyCustomModule :actionRequiredCount="actionRequiredcount"/>
Hope this helps someone.
Most people do have an error here because of:
a typo or something that they forgot to declare/use
the opposite, did it in several places
To avoid the typo issues, I recommend always using Vue VSCode Snippets so that you don't write anything by hand by rather use vbase, vdata, vmethod and get those parts generated for you.
Here are the ones for Vue3.
You can of course also create your own snippets by doing the following.
Also make sure that you're properly writing all the correct names as shown here, here is a list:
data
props
computed
methods
watch
emits
expose
As for the second part, I usually recommend either searching the given keyword in your codebase. So like cmd + f + changeSetting in OP's case to see if it's missing a declaration somewhere in data, methods or alike.
Or even better, use an ESlint configuration so that you will be warned in case you have any kind of issues in your codebase.
Here is how to achieve such setup with a Nuxt project + ESlint + Prettier for the most efficient way to prevent bad practices while still getting a fast formatting!
One other common scenario is:
You have a component (child) extending another component (parent)
You have a property or a method xyz defined under methods or computed on the parent component.
Your are trying to use parent's xyz, but your child component defines its own methods or computed
Sample code with the problem
// PARENT COMPONENT
export default {
computed() {
abc() {},
xyz() {} // <= needs to be used in child component
},
...
}
// CHILD COMPONENT
export default {
extends: myParentComponent,
computed() {
childProprty1() {},
childProprty2() {}
}
}
The solution
In this case you will need to redefine your xyz computed property under computed
Solution 1:
Redefine xyz and copy the code from the parent component
// CHILD COMPONENT
export default {
extends: myParentComponent,
computed() {
xyz() {
// do something cool!
},
childProprty1() {},
childProprty2() {}
}
}
Solution 2
Redefine xyz property reusing parent component code (no code redundancy)
// CHILD COMPONENT
export default {
extends: myParentComponent,
computed() {
xyz() {
return this.$parent.$options.computed.xyz
},
childProprty1() {},
childProprty2() {}
}
}
For me it happened because I wrote method: instead of methods: (plural). It's a silly mistake but it can happen :)
In my case it was the methods: { } I had put the } before my method functions so for example I had it like this methods: { function , function }, function, function so some of the functions that were out of the curly braces were not included inside the methods function.

Vue watcher's first callback not ordered "correctly" on post-instantiation watchers

I have the following doubt, regardless of the quality of the code or the best practices not followed.
I have a Vue component with two watchers. One of them is declared on the component's watch object (Therefore instantiated at its creation), and the other one is instantiated at the mounted(), in order to avoid calling it on the first mutation.
data: {
return() {
watchedAtInit: null,
watchedAfterMount: null,
};
},
mounted() {
api.fetchSomething().then((response) => {
this.watchedAtInit = response.watchedAtInit;
this.watchedAfterMount = response.watchedAfterMount;
this.$watch('watchedAfterMount', this.watchedAfterMountMethod);
});
},
watch: {
watchedAtInit() {
// something
}
}
methods: {
watchedAfterMountMethod() {
this.watchedAtInit = someValue;
},
}
The problem here is at the end: The watched variable watchedAtInit is being mutated from another watcher watchedAfterMountMethod, which I understand is not good practice.
Still, something strange happens when mutating watchedAfterMount:
The first time I mutate watchedAfterMount, the watchedAtInit() watcher is called before the watchedAfterMountMethod(), despite the mutation of watchedAtInit occurs at the watchedAfterMountMethod.
In further watchedAfterMount mutations, the order is indeed as expected: the watchedAfterMountMethod is called, which mutates watchedAtInit, therefore triggering its watcher.
¿Why is the watchedAfterMount watcher delaying at its first mutation? ¿Does it have something to do with Vue's reactivity system, or is it merely the result of an incorrect implementation of watchers?
¿Could I be missing anything else?
Thanks in advance.
Watchers won't be intialized on mounted by default, but you can force this behaviour writing your watchers like this:
watch: {
watchedAtInit: {
immediate: true,
handler() {
// something
}
}
}

vuejs computed setter of given prop is not reactive

I'm using computed to copy my prop value and use/mutate it in my component:
export default {
props: ['propOffer'],
computed: {
offer: {
get: function () {
return JSON.parse(JSON.stringify(this.propOffer))
},
set: function () {
this.offer
}
},
}
The problem is within using setter. It is not reactive. When I use some kind of input, there is a delay, so my computed offer isn't updating instantly. Example of input:
<v-text-field
label="Offer title"
v-model="offer.title"
></v-text-field>
This is far opposite to the behaviour when I declare offer as a variable (wthout computed) - then I got my {{offer}} changes instantly inside the <template>
How can I improve it? Am I setting my computed wrong?
To better understand this situation, this is what happens at the moment:
When the application loads, the initial state is:
<your-component>
propOffer: '{"title":"test"}'
offer.<lastValue>: undefined
At the point in time, your application will load the v-text-field, this references field offer, and this inits the offer computed variable:
<your-component>
propOffer: '{"title":"test"}'
offer.<lastValue>: [Javascript object 1]
[Javascript object 1]
title: "test"
<v-text-field>
value: "test"
As the user types into the v-text-field, its value changes, because the v-model emits back updates:
<your-component>
propOffer: '{"title":"test"}'
offer.<lastValue>: [Javascript object 1]
[Javascript object 1]
title: "test123"
<v-text-field>
value: "test123"
As you can see here, the setter is never invoked in the normal operation, and hence your code to save it does not run.
You can solve this by making another computed prop for the title of the offer, and then adding some code to prevent your changes from being made undone.
Let's start with the getter & setter for the title:
computed: {
title: {
get() {
return this.offer.title;
},
set(title) {
this.offer = {...this.offer, title};
}
},
// ....
Now we need to properly handle this set operation inside our main offer function, because if we don't handle it, and basically modify its returned object, we get into the territory of undefined behaviour, as the value of the computation doesn't match the computation.
// ...
offer: {
get: function () {
if (this.modifiedOffer) {
return this.modifiedOffer;
}
return JSON.parse(JSON.stringify(this.propOffer))
},
set: function (offer) {
this.modifiedOffer = offer;
}
},
},
data() {
return: {
modifiedOffer: undefined,
};
},
After doing this pattern, you now have a stable application, that shows no undefined behaviour, for more functionality, you basicly need to check if the propOffer changes, and either forcefully delete the this.modifiedOffer, or add more logic to a different computed variable that informs the user there is a data conflict, and ask him to overwrite his data.

Component to emit event on mapGetters

So I load my component, I then call the do something like the following:
created() {
this.$store.dispatch('messages/connect');
this.$store.dispatch('messages/fetchAllMessages');
// this.$emit('set-recipient', this.chats[0]);
},
computed: mapGetters('messages', {
chats: 'getMessages'
}),
The commented section within created is the snippet that I would like to run but only on the creation of this.chats and not on any update there after.
If I try to emit the event where it currently is I get an error: Cannot read property '0' of null.
Hopefully you understand what I mean.
Any ideas?
fetchallMessages calls your server to get the messages, right? that asynchonous process won'T be finshed when the meit is run like that.
If you make sure to return a Promise from that action which resolves after you have added chats, you can do this:
this.$store.dispatch('messages/fetchAllMessages')
.then(() => {
this.$emit('set-recipient', this.chats[0]);
})
If you have trouble returning a Promise from that action, share its implementation and we'll fix it.
If i understand correctly, you want to execute this.$emit('set-recipient', this.chats[0]); only after chats was initialized.
You have 2 options:
don't use mapGetters for the chats getter, just define the computed yourself:
computed: {
...mapGetters('messages')
chats(){
const messages = this.$store.getters.getMessages;
if (messages.length){
this.$emit('set-recipient', this.chats[0]);
}
return messages;
}
}
Instead of doing it in the component, you can move the logic to the store and emit the event from there when you modify chats

Emit an event when a specific piece of state changes in vuex store

I have a Vuex store with the following state:
state: {
authed: false,
id: false
}
Inside a component I want to watch for changes to the authed state and send an AJAX call to the server. It needs to be done in various components.
I tried using store.watch(), but that fires when either id or authed changes. I also noticed, it's different from vm.$watch in that you can't specify a property. When i tried to do this:
store.watch('authed', function(newValue, oldValue){
//some code
});
I got this error:
[vuex] store.watch only accepts a function.
Any help is appreciated!
Just set a getter for the authed state in your component and watch that local getter:
watch: {
'authed': function () {
...
}
}
Or you can use ...
let suscribe = store.subscribe((mutation, state) => {
console.log(mutation.type)
console.log(mutation.payload)
})
// call suscribe() for unsuscribe
https://vuex.vuejs.org/api/#subscribe