VueJs/Nuxt Filter an array, error _vm.filtered.. is not a function - vue.js

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)
})
}
},

Related

Filter list while in for loop in Vue

I have this list and I have to display in different divs the active from the list and those who are inactive.
<v-ons-card
v-for="item in items.data"
:key="item.id"
> </v-ons-card>
Items come for this part of the code.
computed: mapState({
items: state => state.items.items
})
state.items looks like this
const state = {
items: {
data: [],
},
};
I'm thinking if I can do this,
v-for="item in items.data.active"
Is there any way I can do that?
I think this is what you're looking for:
computed: {
activeItems() {
return this.items.data.filter((x) => x.active);
},
inactiveItems() {
return this.items.data.filter((x) => !x.active);
},
},
<div>Active items:</div>
<v-ons-card
v-for="item in activeItems"
:key="item.id"
></v-ons-card>
<div>Inactive items:</div>
<v-ons-card
v-for="item in inactiveItems"
:key="item.id"
></v-ons-card>
Use getters in Vuex or computed property.

Can I insert a computed property into a Vue component dynamically

I am generating the contents of a Vue component by iterating through a large array of objects. I'd like to use computed properties to determine whether to show certain nodes, but since the computed reference is used inside a loop, I need to be able to set the reference name dynamically.
Below is a notional example of what I'm trying to do. How can I make showItemX change based on the current item?
<template>
<ul>
<li v-for="item in myArr" v-if="showItemX">
{{ item.name }}
</li>
</ul>
</template>
<script>
export default {
data() {
return {
myArr: [{
id: 'item1',
name: 'Item 1'
}, {
id: 'item2',
name: 'Item 2'
}]
};
},
computed: {
showItem1: function() {
return this.$store.state.showItem1;
},
showItem2: function() {
return this.$store.state.showItem2;
}
}
}
</script>
2 possible solutions
These are the two routes I've considered so far, but I'm not sure which one would be more efficient or if another way would be preferred:
1. Return a single object for the computed property
In this option, the two computed properties above would be combined into a single property:
computed: {
showItem: function() {
return {
item1: this.$store.state.showItem1,
item2: this.$store.state.showItem2
}
}
}
Then the the v-if would be set to showItem[item.id]:
<li v-for="item in myArr" v-if="showItem[item.id]">
{{ item.name }}
</li>
The downside here is that it seems that the entire object gets recomputed each time one of the dependencies changes.
2. Use a method to get the corresponding computed property
Here I tried passing item.id to a method as a way to access the corresponding computed property:
computed: {
item1Show: function() {
return this.$store.state.showItem1;
},
item2Show: function() {
return this.$store.state.showItem2;
}
},
methods: {
showItem: function(id) {
return this[id + 'Show']
}
}
And in the template:
<li v-for="item in myArr" v-if="showItem(item.id)">
{{ item.name }}
</li>
Again, in this example, I'm not sure if I'm fully leveraging the computed properties.
Should one of these options be preferred over the other or is there a better way to accomplish this that I'm missing?
The nice thing about Vue and JavaScript is that you can use whichever approach suits your needs, but, fwiw, I'd probably find something like the following easiest to understand
<li v-for="item in myArr" v-if="showItem(item)">
{{ item.name }}
</li>
And then define the showItem method, e.g.
showItem(item) {
return item.id === "item1" ?
this.$store.state.showItem1 :
this.$store.state.showItem2;
}
That assumes you're not using the computed properties anywhere else not shown in the post
There's a better way.
For possible solution #1, you might as well do
<li v-for="(item, index) in myArr" v-if="$store.state['showItem' + (index + 1)]">
Possible solution #2, you completely miss out on Vue's optimizations.
The method, while not computationally intensive, will re-run for every element each render.
Below is a solution which fits the specific parameters of your example problem. However, it's not actually what I recommend here. More below.
<template>
<ul>
<!--
`:key` is crucial for performance.
otherwise, every element will re-render
whenever the filtered array updates.
-->
<li v-for="item in myFilteredArr" :key="item.id">
{{ item.name }}
</li>
</ul>
</template>
<script>
export default {
data: _ => ({
myArr: [
{
id: 'item1',
name: 'Item 1'
},
{
id: 'item2',
name: 'Item 2'
}
],
}),
computed: {
myFilteredArr () {
/*
abstracting state to a constant avoids
re-running the getter functions each iteration
*/
const state = this.$store.state;
return this.myArr.filter(
(item, index) => state['showItem' + (index + 1)]
);
}
}
}
</script>
My actual recommendation is that you move all this logic into a Vuex getter. You can read about them here: https://vuex.vuejs.org/guide/getters.html .
Since your filtering logic is already being processed in the store, the function which is setting all the showItem's can just be cut and pasted into a Vuex getter, returning myFilteredArr in the same way as above.
This way, there's no component<->store interdependency, and your store's state will be much cleaner.

Unexpected side effect in computed property

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);

Vuex and vuex getters with arguments

I have a small app based on Vue and Vuex. Its just a table with items like that
<div class='items'>
<div v-for='item in items'>
<span> {{ item.name }} </span>
<router-link :to='"update/" + item.id'>Edit</router-link>
</div>
</div>
Items array is loaded from Vuex state using getters. So the problem is that when i press the 'edit' button, it redirects me to another page, where i have a function like that
computed() {
item() {
return this.$store.getters.get_item(this.$route.params.id)
}
}
and generally it should work (i have tested it by passing some numbers instead of "this.$route.params.id") but its not.. why? no errors, nothing, just empty array
my get_item function
getters: {
get_item: (state) => (index) => {
return state.items.filter((item) => {
return item.id === index
}
}
}
You are defining computed as a function while it's supposed to be an object. Try instead:
computed: {
item() {
return this.$store.getters.get_item(this.$route.params.id)
}
}

Vue filter in v-for

Hi I am pretty new to VueJS and have started working on a very simple API request. I have an object that looks like this:
posts: [
text: "String",
choices: {"1":"Yes","2":"No"}
]
Coming from angular, this seems very straightforward. I would just use a filter to convert choices to an object and loop over it. However, I ran into a problem. When I attempt to use the filter 'log' or 'json' in the v-for, they don't work.
<template>
<div>
<li v-for="(post,index) in posts | log">
<ul>
{{ post.text | log }}
{{ post.choices | json }}
<li v-for="(value,key) in post.choices | json">
{{value}} {{key}}
</li>
</ul>
</li>
</div>
</template>
<script>
import {HTTP} from './main';
export default {
filters: {
json: function (value) {
return JSON.parse(value)
},
log: function (value) {
console.log(value)
}
},
props: [
'apiKey'
],
data: () => ({
posts: [],
post: [],
errors: []
}),
created() {
HTTP.get('questions', { headers: { 'Api-Key': this.apiKey} })
.then(response => {
this.posts = response.data
})
.catch(e => {
this.errors.push(e)
})
}
}
</script>
Then no data shows up, however they work fine in the mustache template. Any thoughts on how I can accomplish this?
tl;dr
works:
{{ post.choices | json }}
does not work:
<li v-for="(value,key) in post.choices | json">
Any work around? I can't use computed properties because this is a "sub-object" of an array and computed properties don't work that way, unless I am mistaken?
You cannot add a filter to a v-for directive.
Just parse the data before setting the posts data property:
HTTP.get('questions', { headers: { 'Api-Key': this.apiKey} })
.then(response => {
let posts = response.data;
post.forEach(p => p.choices = JSON.parse(p.choices));
this.posts = posts;
})
I don't see why you need to use JSON.parse, since the choices key refers to a valid JS object. Unless you have made a mistake, and that the choices key refers to a string.
Anyway, if you want to parse post.choice into a valid JS object, you can simply pass it into a custom function which returns the parsed JSON to v-for, for example:
<li v-for="(value,key) in postChoicesJson(post.choice)">
{{value}} {{key}}
</li>
And then declare a method for that:
postChoicesJson: function(obj) {
return JSON.parse(obj);
}
Note: Your DOM is invalid, because <li> elements must be a direct child of a <ul> or an <ol> element.