Can't get data of computed state from store - Vue - vue.js

I'm learning Vue and have been struggling to get the data from a computed property. I am retrieving comments from the store and them processing through a function called chunkify() however I'm getting the following error.
Despite the comments being computed correctly.
What am I doing wrong here? Any help would be greatly appreciated.
Home.vue
export default {
name: 'Home',
computed: {
comments() {
return this.$store.state.comments
},
},
methods: {
init() {
const comments = this.chunkify(this.comments, 3);
comments[0] = this.chunkify(comments[0], 3);
comments[1] = this.chunkify(comments[1], 3);
comments[2] = this.chunkify(comments[2], 3);
console.log(comments)
},
chunkify(a, n) {
if (n < 2)
return [a];
const len = a.length;
const out = [];
let i = 0;
let size;
if (len % n === 0) {
size = Math.floor(len / n);
while (i < len) {
out.push(a.slice(i, i += size));
}
} else {
while (i < len) {
size = Math.ceil((len - i) / n--);
out.push(a.slice(i, i += size));
}
}
return out;
},
},
mounted() {
this.init()
}
}

Like I wrote in the comments, the OPs problem is that he's accessing a store property that is not available (probably waiting on an AJAX request to come in) when the component is mounted.
Instead of eagerly assuming the data is present when the component is mounted, I suggested that the store property be watched and this.init() called when the propery is loaded.
However, I think this may not be the right approach, since the watch method will be called every time the property changes, which is not semantic for the case of doing prep work on data. I can suggest two solutions that I think are more elegant.
1. Trigger an event when the data is loaded
It's easy to set up a global messaging bus in Vue (see, for example, this post).
Assuming that the property is being loaded in a Vuex action,the flow would be similar to:
{
...
actions: {
async comments() {
try {
await loadComments()
EventBus.trigger("comments:load:success")
} catch (e) {
EventBus.trigger("comments:load:error", e)
}
}
}
...
}
You can gripe a bit about reactivity and events going agains the reactive philosophy. But this may be an example of a case where events are just more semantic.
2. The reactive approach
I try to keep computation outside of my views. Instead of defining chunkify inside your component, you can instead tie that in to your store.
So, say that I have a JavaScrip module called store that exports the Vuex store. I would define chunkify as a named function in that module
function chunkify (a, n) {
...
}
(This can be defined at the bottom of the JS module, for readability, thanks to function hoisting.)
Then, in your store definition,
const store = new Vuex.Store({
state: { ... },
...
getters: {
chunkedComments (state) {
return function (chunks) {
if (state.comments)
return chunkify(state.comments, chunks);
return state.comments
}
}
}
...
})
In your component, the computed prop would now be
computed: {
comments() {
return this.$store.getters.chunkedComments(3);
},
}
Then the update cascase will flow from the getter, which will update when comments are retrieved, which will update the component's computed prop, which will update the ui.

Use getters, merge chuckify and init function inside the getter.And for computed comment function will return this.$store.getters.YOURFUNC (merge of chuckify and init function). do not add anything inside mounted.

Related

vue watch handler called without actual changes

In this codepen have a counter that can be incremented with a "incr" link.
I now have a computed property and a watch:
computed: {
test() {
let unused = this.counter;
return [42];
}
},
watch: {
test(val, old) {
// Should I avoid firing when nothing actually changed
// by implementiong my own poor-man's change detection?
//
// if (JSON.stringify(newVal) == JSON.stringify(oldVal))
// return;
console.log(
'test changed',
val,
old
);
}
}
A contrived example perhaps, but in reality this is a calculation where the real data is reduced (in a vuex getter) and most-often, the reduced data doesn't change even when some of the data changes.
Edited to add more detail: The data in the vuex store is normalized. We're also using vue-grid-layout that expects its layoutproperty in a certain non-normalized format. So we have a gridLayout getter that does the vuex -> vue-grid-layout tranform. Watching this gridLayout getter fires even when the resulting gridLayout doesn't actually change, but other details do, such as names and other irrelevant-to-vue-grid-layout object keys in the vuex store.
Now in the above example, when this.counter changes, the watch on test fires too, even though the newVal and oldVal are "the same". They aren't == or === mind you, but "the same" as in JSON.stringify(newVal) == JSON.stringify(oldVal).
Is there any way to have my watch fire only when there are actual changes? Actually comparing JSON.stringify() seems inefficient to me, but I'm worried about performance problems as my project grows as my watch could do expensive operations and I want to ensure I'm not missing something.
According to the Vue.js documentation computed properties are reevaluated when ever a reactive dependency is changed.
In your case the reactive dependency is this.counter
You can achieve the same result by invoking a method as opposed to a computed property.
Just change up your component architecture:
data () {
return: {
counter: 0,
obj: {},
output: null
}
},
watch: {
counter(val, old) {
// Alternatively you could remove your method and do something here if it is small
this.test(val);
},
// Deep watcher
obj: {
handler: function (val, oldVal) {
console.log(val);
console.log(oldVal);
this.test(val);
},
deep: true
},
}
},
methods: {
test() {
let unused = this.counter;
if (something changed)
this.output = [42];
}
}
}
Now in your template (or other computed properties) output is reactive
Read more: https://v2.vuejs.org/v2/guide/computed.html#Computed-Caching-vs-Methods

Error in callback for watcher “get_settings”: “TypeError: Cannot read property ‘general’ of undefined”

Please help me out, how to handle this error i cant seem to handle this out as i am new to vue.
what im doing is getting data from server in store vuex with action. Now in component im accessing that data with getter in computed property and trying to watch that property but on component mount i get that error in console but functionality works fine.
data: function() {
return {
settings_flags :{
general: 0,
privacy: 0,
layouts: 0,
message: 0
}
}
}
1: mounting
mounted() {
let self = this;
self.userid = this.getUserId();
this.$store.dispatch('getGallerySettings',self.req);
self.initial_settings();
}
2: computed
computed: {
get_settings() {
return this.$store.getters.getGallerySettings;
}
}
3: watch
watch: {
'get_settings': {
deep: true,
handler() {
let self =this;
if (this.$_.isMatch(self.get_settings.gallery_settings.general,self.initialSettings.gallery_settings.general) == false) {
self.settings_flags.general = 1;
} else {
self.settings_flags.general = 0;
}
}
}
}
It seems to me that your watcher is looking for a property 'general' that is a child of gallery_settings.
get_settings.gallery_settings.general
In the meantime in data you have a property general that is a child of 'settings_flags'. Those two don't line up. So make sure that either your watcher is looking for something that exists when the component starts up, or tell your watcher to only start watching ' get_settings.gallery_settings.general' when 'get_settings.gallery_settings' actually exists.
if (get_settings.gallery_settings) { do something } #pseudocode
I'm not sure that's your problem, but it might be.

vuex store not refresh computed property

Following the tutorial at this web address http://stackabuse.com/single-page-apps-with-vue-js-and-flask-state-management-with-vuex/, I encountered a problem that the function in the computed property was not automatically invoked after the state in the store was changed. The relevant code is listed as following:
Survey.vue
computed: {
surveyComplete() {
if (this.survey.questions) {
const numQuestions = this.survey.questions.length
const numCompleted = this.survey.questions.filter(q =>q.choice).length
return numQuestions === numCompleted
}
return false
},
survey() {
return this.$store.state.currentSurvey
},
selectedChoice: {
get() {
const question = this.survey.questions[this.currentQuestion]
return question.choice
},
set(value) {
const question = this.survey.questions[this.currentQuestion]
this.$store.commit('setChoice', { questionId: question.id, choice: value })
}
}
}
When a radio button in the survey questions is chosen, selectedChoice will change the state in the store. However surveyComplete method was not called simultaneously. What's the problem? Thanks in advance!
surveyComplete() method does not 'spy' your store, it will be updated, when you change this.survey.questions only. So if you modify the store, nothing will happen inside surveyComplete. You may use the store inside the method.

Declaring variable in "data" section as an alias to $root is not reactive

I declare variable in main.js:
data: {
globalData: {}
}
I want to avoid using this.$root.globalData all the time — so I use local variable in a component as an alias to "global variable":
data() {
return {
localAlias: this.$root.globalData,
}
}
Then I fetch global variable from a server in main.js (simulate by setTimeout):
create() {
window.setTimeout(() => {
this.globalData = {a:1, b:2};
}, 1500);
}
And localAlias remains equal to initial value.
How to make it work? I don't need Vuex yet, I just grab data from server and use it read-only.
Example
Instead of using data you can use computed. It will solve your problem.
computed: {
localAlias: function() {
return this.$root.globalData;
}
}
I have updated the example
The reason localAlias doesn't change is because it still points to the same object, while you re-point this.$root.globalData to a new object. One way to do it is of course to use computed as the other answer suggested. Another way to solve it it to just change the properties instead of re-binding the entire object:
create() {
window.setTimeout(() => {
this.globalData.a = 1;
this.globalData.b = 2;
}, 1500);
}
This is less versatile though and will scale worse if the object becomes bigger.

Watch all properties of a reactive data in Vue.js

I had an API call to the backend and based on the returned data, I set the reactive data dynamically:
let data = {
quantity: [],
tickets: []
}
api.default.fetch()
.then(function (tickets) {
data.tickets = tickets
tickets.forEach(ticket => {
data.quantity[ticket.id] = 0
})
})
Based on this flow, how can I set watcher for all reactive elements in quantity array dynamically as well?
You can create a computed property, where you can stringify the quantity array, and then set a watcher on this computed property. Code will look something like following:
computed: {
quantityString: function () {
return JSON.stringify(this.quantity)
}
}
watch: {
// whenever question changes, this function will run
quantityString: function (newQuantity) {
var newQuantity = JSON.parse(newQuantity)
//Your relevant code
}
}
Using the [] operator to change a value in an array won't let vue detect the change, use splice instead.