Vue.js2.0 - vue reactivity confusion - vue.js

In my project , i have a shoppinglists Array to get displayed. When the component is mounted, the store is populated ( it' conatins only one array for the logged customer, fetched from the API db server... wo any problem)
On dissplay, I get the following message :
vue.esm.js?efeb:571 [Vue warn]: Property or method "shoppinglists" 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.
The shoppinglists property is defined as computed ...
computed: {
...mapGetters([ { shoppinglists: 'getShoppingLists' } ])
},
And the store contains the shoppinglists array
STATE
{
"shoppinglists":
[{"title":"Groceries","items":[{"text":"Bananas","checked":true},
{"text":"Apples","checked":false}],"id":1,"userId":1}],
"isAuthenticated":true,
"currentUserId":1
}
If I insert a prop declaration in data :
data: function () {
return {
shoppinglists: []
}
},
the warning disappear, but still theres is no list displayed..
what could be wrong ?
thanks for feedback
not exactly duplicated question, but not far from this one

It looks like you have mixed the two different options for mapGetters().
You can either write it like this:
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
// mix the getters into computed with object spread operator
...mapGetters([
'doneTodosCount',
'anotherGetter',
// ...
])
}
}
Which maps this.doneTodosCount to this.$store.doneTodosCount and so on.
Or you could do it this way, which is probably what you want:
...mapGetters({
// map `this.doneCount` to `store.getters.doneTodosCount`
doneCount: 'doneTodosCount'
})
And for your example this becomes:
computed: {
...mapGetters({ shoppinglists: 'getShoppingLists' })
},
More documentation and source of the examples are at the bottom of this article.

Related

How to use `mapGetters` in the computed section only, without passing it to data ? And what name should I pass to the set?

This is a screenshot from another question about mapGetters
Link to the question I took the screen from
In Vue.js I saw an answer to a post.
It said :
In your Component
computed: {
...mapGetters({
nameFromStore: 'name'
}),
name: {
get(){
return this.nameFromStore
},
set(newName){
return newName
}
}
}
And I wonder why newName is a "new" name ? And also what it looks like to call the getter in the template html section. Do we use the nameFromStore like a data in the template ?
I tried to see how getters are used but what I found didn't look like this structure. The things I found looked like someone using a value in data that returns this.$store.getters.nameFromStore but I don't want to use the data and do it all in the computed instead like the picture I attached if someone can please help me ?
If you simply want to use mapGetters to access data store in vuex state you can do it as you have it above, without the get/set computed. I'm not sure what the intention of that get/set computed property was but it doesn't look like it would work as expected.
Try this:
// vuex index.js
// ...
getters: {
getAnimals: state => {
return state.items.filter(i => i.type === 'animal');
}
}
// your component.vue
import { mapGetters } from 'vuex'
export default {
// ...
computed: {
...mapGetters(['getAnimals']) // access using this.getAnimals
// or
...mapGetters({ animals: 'getAnimals' }) // access using this.animals
}
}
The bennefit to using it this way is being a computed property, when the items change in vuex state, they will be recomputed, and your component will react to this change. Hopefully this is a clear explanation.
The vue docs:
https://vuex.vuejs.org/guide/getters.html#method-style-access

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.

How to maintain similar variable name in component & in state - VUEJS?

I'm going to use a similar variable in state and in a component as well like below,
store.js
{
export default new Vuex.Store({
state: {
title: 'Component Block'
}
})
}
ComponentA.vue
export default {
data() {
return {
title: ''
}
},
computed: {
...mapState(['title'])
}
}
Is there any way to differentiate either state variable or component variable without changing the variable names causing of similar name?
Thanks,
You can't have a shared name across data/methods/computed properties at the moment in Vue.
If you are wedded to using a data property call title, you can key your mapState:
...mapState({
titleFromStore: 'title'
})
But it's worth considering why we can't use the same name. It would add a layer of confusion to your code. When referring to this.title, which one would you be referring to? This limit is there for a good reason, I'd recommend renaming the data property.

Computed function running without to call it

I'm setting an array in my data property through a computed function and it's working. But I wonder how is possible if I don't call it anywhere?
If I try to add a console.log in my function it doesn't print anything, but it's still setting my data, how is that possible?
My data:
data() {
return {
projects: []
};
},
My computed:
computed: {
loadedProjects() {
console.log("Hello there")
this.projects = this.$store.getters.loadedProjects
}
},
I expect that it doesn't run because I'm not calling, and if it is running(I don't know why) to print the console.log before to set my data. Any clarification?
Thanks:)
You're confusing computed props with methods. If you want to have a method like above that sets a data value of your vue instace, you should use a method, not a computed prop:
data() {
return {
projects: []
};
},
methods: {
loadProjects() {
console.log("Hello there")
this.projects = this.$store.getters.loadedProjects
}
}
This would get the value of this.$store.getters.loadedProjects once and assign it to your local projects value. Now since you're using Vuex, you probably want your local reference to stay in sync with updates you do to the store value. This is where computed props come in handy. You actually won't need the projects in data at all. All you need is the computed prop:
computed: {
projects() {
return this.$store.getters.loadedProjects
}
},
Now vue will update your local reference to projects whenever the store updates. Then you can use it just like a normal value in your template. For example
<template>
<div v-for='item in projects' :key='item.uuid'>
{{item.name}}
</div>
</template>
Avoid side effects in your computed properties, e.g. assigning values directly, computed values should always return a value themselves. This could be applying a filter to your existing data e.g.
computed: {
completedProjects() {
return this.$store.getters.loadedProjects.filter(x => x.projectCompleted)
},
projectIds() {
return this.$store.getters.loadedProjects.map(x => x.uuid)
}
}
You get the idea..
More about best practices to bring vuex state to your components here: https://vuex.vuejs.org/guide/state.html
Computed props docs:
https://v2.vuejs.org/v2/guide/computed.html
You should check Vue docs about computed properties and methods
and shouldn't run methods inside computed property getter
https://v2.vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods
Instead of a computed property, we can define the same function as a method. For the end result, the two approaches are indeed exactly the same. However, the difference is that computed properties are cached based on their reactive dependencies. A computed property will only re-evaluate when some of its reactive dependencies have changed.

How to handle the state when multiple components use the same actions in Vuex for fetching data?

I have been using Vuex to control my whole app state for a while. But now I am facing a problem I have never meet before.
The Workflow is like this:
The root component will fetch data from my database, which is an array with multiple objects in it.
I created child-component and use v-for to show this array to user (that means, each child-component represents an object in the array later)
The Problem comes, when I try to fetch async data for each child-component, for the data-fetching, I also need parameter which come from the array I mentioned.
I use the Vuex actions in the created hook of my child components. The actions fetch_sim_Type will take the payload (which is parameter from parent-component, also from the initial array). And change the state simType in the state: {} of Vuex.
By using the computed properties I can get the fetched simType in my child-component and show it to the user. Everything works so far.
CODES:
THE initial Array (simSumArray in Parent-component) looks like this:
[
{ typeid: 1, name: XXX },
{ typeid: 2, name: ZZZ },
{ typeid: 3, name: GGG },
{ typeid: 4, name: JJJ },
]
PARENT-COMPONENT:
<template
v-for="(singleSim, idx) in simSumArray"
>
<sim-single :singleSim="singleSim"></sim-single>
</template>
CHILD-COMPONENTS:
props: ['singleSim'],
created () {
store.dispatch('fetch_sim_Type', this.singleSim.typeid);
},
computed: {
simType () {
console.log("store.getters.get_simType: ", store.getters.get_simType)
return store.getters.get_simType;
}
},
IN VUEX:
state: {
simType: 'unknown'
},
actions: {
fetch_sim_Type (context, typeid) {
//.. fetch the data based on typeid from DB
context.state.simType = fetchedData;
}
}
But it only works when in the array only when object exist. When there are more than one child-component being created. The state simType in the Vuex store.js will be replaced many times and in every child-component the simType() is always the same.
The Problem is kind of hard to describe. The central problem is, the state in Vuex is meant to be shared everywhere in the whole app, so if I have multiple child-components, they all fetch data for themself, than the shared state will be replaced all the time and I can't get individual state for every child-components.
I don't know if I describe the problem clair but I really tried hard to.
Maybe There is a better way to do this data fetching job without Vuex or maybe I just used the Vuex by the wrong way.
I am sure this should not be a hard question. But I can't find any relative answer online.
Reading your code, the behaviour you describe is normal. I see two solution to your problem (solution 2 is probably closer to what you are looking for) :
Solution 1 - store simType in your component
if you need to access the simType from somewhere else than inside your component and have it stored in your state, skip to solution 2
When your component is created, store the simtype in the component's data. This would look like this:
In your component:
data () {
return {
simType: undefined //declare simType as one of your component's data property
}
},
created () {
store.dispatch('fetch_sim_Type', this.singleSim.typeid).then(simType => {
this.simType = simType //store the fetched simType
})
}
In your vuex Actions:
actions: {
fetch_sim_Type (context, typeid) {
//.. fetch the data based on typeid from DB
return fetchedData //pretty much the same except return the result
}
}
Solution 2 - store simTypes in your state indexed by their IDs
Store your fetched simType by id, like this:
state: {
simTypes: {} //simTypes is now plural, and is an empty object at first. It will later contain several simTypes, indexed by their respective Ids
},
actions: {
fetch_sim_Type (context, typeid) {
//.. fetch the data based on typeid from DB
context.state.simType[typeid] = fetchedData; // a fetched simtyped will be store in simTypes and can be accessed with the typeid as a key
}
}
to retrieve a simType, you can write a vuex getter like this:
getters: {
getSimTypeById: (state) => (typeId) => {
return state.simTypes[typeId]
}
}
So in your example, the computed method would be :
computed: {
simType () {
console.log("store.getters.getSimTypeById(this.singleSim.typeid): ", store.getters.getSimTypeById(this.singleSim.typeid)
return store.getters.getSimTypeById(this.singleSim.typeid);
}
},
This solution, as a bonus, allows you to fetch a simType only once if several of your items have the same simType.
I have had success by keeping shared data in the Vuex store, and watching it from my components.
Although not a best practice, I sometimes don't even bother to use actions or commits to change the state, and just modify the state directly. In this scenario, Vuex just acts like a shared data object for all my components.
Vue Store
state: {
myvalue: []
}
Components
watch: {
'$store.state.myvalue'(value) {
}
}