how to get a deep computed value - vuejs2

i have this deep getter:
getTotalVideoLength: {
cache: false,
get() {
let duration = 0;
_.forEach(this.videos, (video) => {
duration = duration + video.duration;
});
console.log(duration);
return duration;
}
},
In the begin it logs undefined but after a few milliseconds it logs a number, but when i pass that 'getTotalVideoLength' as a prop to a child component it is always NaN.
Any help is welcome!

I'm thinking you may need to change your approach a bit. I'm sure you can make your code work with a computed property, but why force it with a hacky approach? Is there any reason why you need to use a computed value for this? What if you tried it with a data-property and method and called the method onMount to set the data-property so that you can guarantee that the values are already prepared before it's set? Try this code out and let me know how it works, please.
data() {
totalVideoLength: 0,
},
methods: {
setTotalVideoLength(videos) {
this.totalVideoLength = videos.reduce((accumulator, { duration }) => accumulator + duration, 0);
},
},
onMount() {
this.setTotalVideoLength(this.videos);
}

Related

How to create getters and setters for all sub-properties of a Vuex state property efficiently?

I couldn't find the answer anywhere.
Let's say we have Vuex store with the following data:
Vuex store
state: {
dialogs: {
dialogName1: {
value: false,
data: {
fileName: '',
isValid: false,
error: '',
... 10 more properties
}
},
dialogName2: {
value: false,
data: {
type: '',
isValid: false,
error: '',
... 10 more properties
}
}
}
}
Dialogs.vue
<div v-if="dialogName1Value">
<input
v-model="dialogName1DataFileName"
:error="dialogName1DataIsValid"
:error-text="dialogName1DataError"
>
<v-btn #click="dialogName1Value = false">
close dialog
</v-btn>
</div>
<!-- the other dialogs here -->
Question
Let's say we need to modify some of these properties in Dialogs.vue.
What's the best practices for creating a getter and setter for every dialog property efficiently, without having to do it all manually like this:
computed: {
dialogName1Value: {
get () {
return this.$store.state.dialogs.dialogName1.value
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.value', value: value })
}
},
dialogName1DataFileName: {
get () {
return this.$store.state.dialogs.dialogName1.data.fileName
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.data.fileName', value: value })
}
},
dialogName1DataIsValid: {
get () {
return this.$store.state.dialogs.dialogName1.data.isValid
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.data.isValid', value: value })
}
},
dialogName1DataIsError: {
get () {
return this.$store.state.dialogs.dialogName1.data.error
},
set (value) {
this.$store.commit('SET', { key: 'dialogs.dialogName1.data.error', value: value })
}
},
... 10 more properties
And this is only 4 properties...
I suppose I could generate those computed properties programmatically in created(), but is that really the proper way to do it?
Are there obvious, commonly known solutions for this issue that I'm not aware of?
getters can be made to take a parameter as an argument - this can be the 'part' of the underlying state you want to return. This is known as Method-style access. For example:
getFilename: (state) => (dialogName) => {
return state.dialogs[dialogName].data.fileName
}
You can then call this getter as:
store.getters.getFilename('dialogName1')
Note that method style access doesn't provide the 'computed property' style caching that you get with property-style access.
For setting those things in only one central function you can use something like this:
<input
:value="dialogName1DataFileName"
#input="update_inputs($event, 'fileName')">
// ...
methods:{
update_inputs($event, whichProperty){
this.$store.commit("SET_PROPERTIES", {newVal: $event.target.value, which:"whichProperty"})
}
}
mutation handler:
// ..
mutations:{
SET_PROPERTIES(state, payload){
state.dialogName1.data[payload.which] = payload.newVal
}
}
Let me explain more what we done above. First we change to v-model type to :value and #input base. Basically you can think, :value is getter and #input is setter for that property. Then we didn't commit in first place, we calling update_inputs function to commit because we should determine which inner property we will commit, so then we did send this data as a method parameter (for example above code is 'fileName') then, we commit this changes with new value of data and info for which property will change. You can make this logic into your whole code blocks and it will solved your problem.
And one more, if you want to learn more about this article will help you more:
https://pekcan.dev/v-model-using-vuex/

Vue.js computed value returning undefined

I have been having trouble figuring out what I am doing wrong with this code.
name: 'StatementInfo',
data() {
return {
currentStatement: {
client: '',
clientEmail: '',
date: '',
hours: '',
hourlyRate: '',
total: this.calcTotal
}
}
},
computed: {
calcTotal () {
return parseInt(this.hours) * parseInt(this.hourlyRate)
}
},
methods: {
saveForm () {
console.log(this.currentStatement)
this.$emit('save-form', this.currentStatement)
},
}
Console logging this.currentStatement causes total to be undefined, but from a junior devs perspective, I would have imagined this to work. Can any more experienced people take a gander at this and tell me what it could be?
That's not how reactivity works in relation to computed methods.
The content of data is evaluated once and at that point this.calcTotal is undefined. That value will remain like that until you update it.
You should directly use your computed method when you need it.
So use calcTotal instead of total.
Try to assign default values to this.hours and this.hourlyRate = 0,
maybe when you execute saveForm(), that variables are null or undefined

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.

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

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.

Vuejs deep nested computed properties

I'm not really understanding where to put function() { return {} } and where not to when it comes to deeply nesting computed properties.
By the way, this is in a component!
computed: {
styles: function() {
return {
slider: function() {
return {
height: {
cache: false,
get: function() {
return 'auto';
}
},
width: {
cache: false,
get: function() {
return $('#slideshow').width();
}
}
}
}
}
}
},
This is returning undefined. When I get rid of the function() { return {} } inside of the slider index, it returns an object when I do styles.slider.width instead of the get() return. It just shows an object with cache and get as indexes..
Thanks for any help!
The reason I'm asking is because I have multiple nested components that involve styling from the parent. Slider, tabs, carousels, etc. So I wanted to organize them like this.
I believe you mean to return a computed object, but not actually structure the computation in a nested manner?
What the others have said regarding the 'computed' hook not having syntax for nesting is correct, you will likely need to structure it differently.
This may work for you: I generate many objects in a similar fashion.
computed: {
computedStyles(){
var style = {slider:{}}
style.slider.height = 'auto'
style.slider.width = this.computedSlideshowWidth
return style
},
computedSlideshowWidth(){
return $('#slideshow').width()
}
As per 2020 and Vue 2.6.12 this is completelly possible. I believe this has been possible since v.2 but cannot confirm.
Here is the working example:
this.computed = {
// One level deep nested,
// get these at `iscomplete.experience`
// or `iscomplete.volume`
iscomplete: function() {
return {
experience: this.$data.experience !== null,
volume: this.$data.volume > 100,
// etc like this
};
},
// More levels deep nested.
// Get these at `istemp.value.v1 and `istemp.value.v2`
istemp: function() {
return {
value1: {
v1: this.$data.experience,
v2: 'constant'
}
}
}
};
As a result you will be able to access your deep nested computed in your template as e.g. follows <span v-text="iscomplete.experience"></span> that will output <span>true</span> for the first example computed above.
Note that:
Since Vue v.2 cache key is deprecated;
Vue would not execute functions assigned to a computed object nested keys;
You cannot have computed for non-Vue-reactive things which in your case is e.g. $('#slideshow').width(). This means they are not going to be re-computed on their content change in this case (which is a computed's sole purpose). Hence these should be taken away from computed key.
Other than that I find nested computeds to be quite helpful sometimes to keep things in better order.