I'm not sure why i'm getting an unexpected side effect in computer property error with the code below.
Error:
✘ https://google.com/#q=vue%2Fno-side-effects-in-computed-properties Unexpected side effect in "orderMyReposByStars" computed property
src/components/HelloWorld.vue:84:14
return this.myRepos.sort((a, b) => a.stargazers_count < b.stargazers_count)
html:
<div v-if="myRepos && myRepos.length > 0">
<h3>My Repos</h3>
<ul>
<li v-for="repo in orderMyReposByStars" v-bind:key="repo.id">
<div class="repo">
{{repo.name}}
<div class="pull-right">
<i class="fas fa-star"></i>
<span class="bold">{{repo.stargazers_count}}</span>
</div>
</div>
</li>
</ul>
</div>
js:
export default {
name: 'HelloWorld',
data () {
return {
myRepos: null, <-- THIS IS ULTIMATELY AN ARRAY OF OBJECTS
}
},
computed: {
orderMyReposByStars: function () {
return this.myRepos.sort((a, b) => a.stargazers_count < b.stargazers_count)
},
...
From what I can tell this looks correct according to the docs here https://v2.vuejs.org/v2/guide/list.html#Displaying-Filtered-Sorted-Results
.sort mutates the original array.
To avoid it, clone the array before sorting it.
.slice() is 1 of the simplest way to clone array. See https://stackoverflow.com/a/20547803/5599288
return this.myRepos.slice().sort((a, b) => a.stargazers_count < b.stargazers_count)
on a side note, null.sort() or null.slice() will throw error. Perhaps it would be better to set the initial value of myRepos to empty array [] instead of null
2021 easy & readable solution...
Just the use spread syntax:
computed: {
orderMyReposByStars: function () {
return [...this.myRepos].sort((a, b) => a.stargazers_count < b.stargazers_count)
},
}
My Solution is to add a filter before the sort.
Array.filter(() => true).sort((a, b) => a.menu_order - b.menu_order);
Related
I am new to Vue/Nuxt and try to filter an array.
computed: mapState({
entries: state => state.archives.archives
}),
filteredArchive, function (objects, key) {
if (objects) {
return objects.filter(function(object) {
return object.tag === key
})
}
I want to get the result in a loop:
<li v-for="(entry, index) in (entries | filteredArchive('test'))">{{ entry.title }}</li>
This fails..
What is wrong in my approach..
Thanks for help.
I suggest creating a computed property for "filteredArchive" instead of a filter. In fact, I believe filters are going away in Vue 3. You can put it in a mixin if you need to share the logic across components.
ok. this is my solution for now:
<ul>
<li v-for="(entry, index) in filteredByTag(entries, 'test')">
<nuxt-link :to="'archive/' + entry.id">{{ entry.title }}</nuxt-link>
</li>
</ul>
computed: mapState({
entries: state => state.archives.archives,
}),
methods: {
filteredByTag(entries, tag){
return entries.filter((entry) => {
return entry.tag.match(tag)
})
}
},
I am a beginner to Vue JS. I have to use a variable inside a component whose value changes often.
So when I declare and define it under data() the following warn is coming in Chrome console
Since when there is a change in data() variables automatically Vue framework calls render function.
Is there any way to declare and use a variable other than declaring it in data() method ??
<template>
<ul>
<div v-for="(list,index) in itemlist" :key="index">
<div v-if="!isFirstCharSame(list.label)" >{{ firstChar }} </div>
<li>
<span>{{ list.label }}</span>
</li>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
itemlist: [
{"label":"Alpha"},
{"label":"Beta"},
{"label":"Charlie"},
{"label":"Delta"}],
firstChar:"$"
}
},
methods : {
isFirstCharSame: function(str) {
if(str.startsWith(this.firstChar)) {
return true;
}
this.firstChar = str.charAt(0);
return false;
}
}
}
</script>
Expected output should be like this
Inside Group A It should display all the elements starting with A
Below we will render using a computed property to make sure its sorted alphabetically and then render your first char. Though You should be using grouping imo.
<template>
<ul>
<div v-for="(list, index) in sortedlist" :key="`people_${index}`">
<div v-if="!isFirstCharSame(list.label)" >{{ firstChar }} </div>
<li>
<span>{{ list.label }}</span>
</li>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
itemlist: [
{"label":"Alpha"},
{"label":"Beta"},
{"label":"Charlie"},
{"label":"Delta"},
],
firstChar: '',
};
},
methods: {
isFirstCharSame(char) {
if (str.startsWith(this.firstChar)) {
return true;
}
this.firstChar = str.charAt(0);
return false;
},
},
computed: {
sortedList() {
return this.itemList.sort((a, b) => {
if (a.label > b.label) {
return 1;
}
if (b.label > a.label) {
return -1;
}
return 0;
});
},
},
};
</script>
And yes, You can update your data any time you wish and the component will do a re render to reflect it.
You can declare variables in your component within your methods or inside computed properties, etc., but they won't be reachable from the template or the rest of the code nor they would be reactive.
The only way for them to be reactive and reachable from the higher scope is adding the data property to the component in the following way:
data: function () {
return {
foo: 'bar'
}
},
or
data () {
return {
foo: 'bar'
}
},
Besides this, the reason of your error is that you are mutating the state of your variables inside the render. When this happens, Vue re-renders the template because the values have mutated and calls again to the function and voilà: there you have an infinite loop.
You should probably check the function you are calling and try to replace the changing variables from the data property with local variables that take their data from the actual data variables.
I have a simple number array generated at random that is rendered by a v-for, I also want to be able to filter it by writing the desired numbers in a input, I do this by using the vanilla JS filter() method. However it returns the error
TypeError: "num.includes is not a function"
I don't know what am I doing wrong, here's the html:
new Vue({
el: "#app",
data: {
numberArray: [],
searching: ''
},
methods: {
random() {
this.numberArray = Array.from({
length: 40
}, () => Math.floor(Math.random() * 40));
},
search(){
return this.numberArray.filter((num) => num.includes(this.searching));
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<button #click="random()">
Generate
</button>
<hr>
<input type="search" v-model="searching" placeholder="search">
<ul>
<li v-for="num in search">
{{ num }}
</li>
</ul>
</div>
includes() is a function defined on strings and arrays, not numbers.
Also search should be a computed property instead of a method.
Did you mean to do this:
computed: {
search() {
return this.numberArray.filter(num => String(num).includes(this.searching));
}
}
I have a little issue with setting element's width by using v-bind:style=...
The deal is that properties for style are requried faster, than I can provide them (in mounted). Any idea how to force update after I will fill my array with width's?
<template>
<div>
<div class="headings ">
<div class="t-cell head" v-for="(header, index) in headings"
:style="'min-width:'+ getHeight(index) +'px'"
>
{{header}}
</div>
</div>
</div>
<div class="fixed-table text-inline" >
<div class="t-cell head" v-for="(header, index) in headings" :ref="'head' + index">
{{header}}
</div>
</div>
</div>
</template>
<script>
export default {
mounted: function(){
this.getColumnWidths();
},
methods: {
getHeight(index){
return this.headerWidths[index];
},
getColumnWidths(){
const _that=this;
this.headings.forEach(function(element,index){
_that.headerWidths[index] = _that.$refs['head'+index][0].clientWidth
});
},
},
data: function () {
return {
headings: this.headersProp,
headerWidths:[],
}
}
}
</script>
It would be great if there would be some method to enforce update, as the width will probably change based on the content inserted.
Fiddle
https://jsfiddle.net/ydLzucbf/
You are being bitten by the array caveats. Instead of assigning individual array elements (using =), use vm.$set:
getColumnWidths() {
const _that = this;
this.header.forEach(function(element, index) {
_that.$set(_that.headerWidths, index, _that.$refs['head' + index][0].clientWidth)
});
console.log(this.headerWidths);
},
I'm working on a vueJS file and have a component :
<div class="panel-heading" v-on:click="displayValue(feature.id)">
{{ feature.nom }}
</div>
<div class="panel-body" v-if="getDisplay(feature.id)">
foo
</div>
my function displayValue(id) :
displayValue(id){
for(let i =0; i<this.product_site.displayFeatures.length;i++){
if (this.product_site.displayFeatures[i].idFeature === id){
this.product_site.displayFeatures[i].show = !this.product_site.displayFeatures[i].show
}
}
console.log(this.product_site.displayFeatures)
},
First I am not very happy with that. I trie to do a .find but it didn't work :
this.product_site.displayFeatures.find(function(a){
a.idFeature === id
}).show = true
But I had an error telling me can not read 'show' of undefined
and my function getDisplay(id)
getDisplay(id){
this.product_site.displayFeatures.forEach(function(a){
if(a.idFeature === id){
return a.show
}
})
}
same as before if I try with a find.
Anyway, I thought it would work with that, but when I do console.log(this.product_sites.displayFeatures) I have the array with the modified value but foo is not shown