Undefined id when trying to add an id dynamically in Vue.js - javascript

This a simple to-do list and I am trying to add an id in each <li> that is rendered. Each id should be named different (obviously) but similar name: "id0","id1","id2"...etc
The number comes from the directive v-for="i in items"
I do it like this:
<li :id="`id${i.id}`" v-for="i in items" :key="`id${i.id}`">
Complete code:
<ul class="item-list-ul">
<li :id="`id${i.id}`" v-for="i in items" :key="`id${i.id}`">{{ i }}
<div class="item-butons">
<b-button class="done-btn" #click="strikeItem(i)" size="sm" variant="outline-dark">DONE!</b-button>
<b-button class="delete-btn" #click="deleteItem(i)" size="sm" variant="warning">Delete</b-button>
</div>
</li>
</ul>
My items array:
data () {
return {
items: ["five", "<li>", "should","be","rendered"]
}
}
But when I check in the console the names of the new dynamically created id´s of the <li> they just appear idundefined when in the case of having for example 3 <li> they should appear like this:
id0
id1
id2
However in the console there aren't any errors. It seems that the vue-html simply does not read a number in ${i.id} but just undefined. Why?

If items is just an array of strings, then there is no id property available. The standard idiom in this case is to use the index like so:
<li v-for="(i, index) in items" :id="`id${index}`" :key="index">
Alternatively, you could reshape your data to have ids like:
[{ id: 0, text: 'foo' }, { id: 1, text: 'bar' }, { id: 2, text: 'baz' }]
That could be done as part of a computed property, for example. Note this would also require a few changes to your template.

Related

Values are changing in loop but not passing to the function which i called in #click in vue [duplicate]

I have used vue.js for a couple of projects and I have been using the index as the key in the for loops
<div v-for="(item, index) in items" :key="index"></div>
...and have started to wonder if there are problems with that since examples usually use the ID of the item.
<div v-for="(item, index) in items" :key="item.ID"></div>
Because arrays are mutable. The index of any given item can and will change if items are added to or removed from the array.
You want your key to be a unique value identifying only your unique component. A primary key that you create is always better than using an index.
Here is an example.
console.clear()
Vue.component("item", {
props: ["value"],
data() {
return {
internalValue: this.value
}
},
template: `<li>Internal: {{internalValue}} Prop: {{value}}</li>`
})
new Vue({
el: "#app",
data: {
items: [1, 2, 3, 4, 5]
},
methods: {
addValue() {
this.items.splice(this.items.length / 2, 0, this.items.length + 1)
}
}
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
{{items}}
<ul>
<item v-for="i in items" :value="i" :key="i"></item>
</ul>
<button #click="addValue">AddValue</button>
<ul>
<item v-for="(i, index) in items" :value="i" :key="index"></item>
</ul>
</div>
Note that when addValue is clicked, the list on top represents the new numbers in the array where the truly are in the array; in the middle. In the second list below the button, the values do not represent the actual location in the array and the internal and property values do not agree.
From the Vue docs (emphasis mine): https://vuejs.org/guide/essentials/list.html#maintaining-state-with-key
To give Vue a hint so that it can track each node's identity, and thus reuse and reorder existing elements, you need to provide a unique key attribute for each item
If the index of any item in the array is changed (e.g. by adding/removing a new item anywhere other than the end of the array), then Vue will lose track of the item.
For example:
let data = [A, B, C, D]
<div v-for="(item, index) in data" :key="index">
Vue tracks each item like this:
A: 0
B: 1
C: 2
D: 3
If you remove B from the array, Vue will then track each item like this:
A: 0
C: 1
D: 2
The indices of C and D have changed, so Vue has lost track of them, and will remove D from the rendered output instead of B (because from its point of view, it is the item with index 3 that was removed).
This is why the key should uniquely identify an item, which an index does not do.
However, it also means that there are some cases where using the index as the key is acceptable:
The array will not change
Or the array will only have items added/removed at the end and will not be reordered
Or the items are all identical
If you cannot use the index as a the key, but cannot uniquely identify items (e.g. some of the items in the list are identical), a solution may be to use lodash's uniqueId():
<div v-for="item in data" :key="uniqueId()">
console.clear()
Vue.component("item", {
props: ["value"],
data() {
return {
internalValue: this.value
}
},
template: `<li>Internal: {{internalValue}} Prop: {{value}}</li>`
})
new Vue({
el: "#app",
data: {
items: [{name:'a'},{name:'b'},{name:'c'}]
},
methods: {
addValue() {
this.items = [{name:'a'},{name:6},{name:'b'},{name:'c'}];
}
}
})
<script src="https://unpkg.com/vue#2.2.6/dist/vue.js"></script>
<div id="app">
{{items}}
<ul>
<item v-for="i in items" :value="i.name" :key="i"></item>
</ul>
<button #click="addValue">AddValue</button>
<ul>
<item v-for="(i, index) in items" :value="i.name" :key="index"></item>
</ul>
</div>
To be more clear

Should I use v-show with v-for

I know that "v-if" must avoid with "v-for" but not sure about "v-show" because it is just to toggle the display attribute.
This is the code in case anyone wants to know. Basically, I try to switch 3 different types of filter list. The code runs fine but I just wanna know if it should be avoid like "v-if".
<template>
<button
v-for="(filter, index) in filterList" :key="index"
#click="chosenFilter = filter.name"
>
{{ filter.name }}
</button>
<div
v-for="(filter, index) in filterList" :key="index"
v-show="chosenFilter === filter.name"
>
<div v-for="(listItem, index) in filter.list" :key="index">
{{ listItem }}
</div>
</div>
</template>
<script>
data () {
return {
filterList: [
{ name: 'Type 1', list: [] },
{ name: 'Type 2', list: [] },
{ name: 'Type 3', list: [] }
],
chosenFilter: 'Type 1'
}
}
</script>
From the official style guide: https://v2.vuejs.org/v2/style-guide/#Avoid-v-if-with-v-for-essential
There are 2 points in which it's not a good practice to use that, the 2nd one is interesting: To avoid rendering a list if it should be hidden. This one is basically fine since you're not doing heavy JS rendering, just basic CSS toggling.
So yeah, I'd say it's correct to have a v-show (and ESlint is not complaining btw).
But IMO, you can solve this kind of behavior in pretty much all cases with a computed: your filter button could be selected with an ID and your list rendering could be filtered with a filter here.
Replace #click="chosenFilter = filter.name" with #click="chooseFilter and get the ID (thanks to $event) of the item you've clicked on, then filter your list with the selected filter.

Apply class conditionally to specific item in a list Vue.js 3.0

I'd like to disable specific link in automatically rendered list in Vue.js conditionally, here is what I have at the moment:
<ul>
<li class="nav-item" :key="id" v-for="(item, id) in listOfItems">
<a class="nav-link" :id="item.id">{{ item.name }}</a>
</li>
</ul>
Basically in some conditions I'd like to disable one of the links from the listOfItems and get it back active if this condition is not applicable anymore. If I bind conditional class it applies to each item in the list:
:class="[someCondition = 'something' ? 'disabled' : '']"
How to specify in this condition which item exactly should be disabled if the condition is true?
You could put an additional property on any items you want checked that way:
listOfItems: [
{ id: 1, name: 'name1', checkme: true },
{ id: 2, name: 'name2', checkme: false },
]
Then, using object binding syntax and === since you're doing a comparison:
:class="{ disabled: item.checkme && someCondition === 'something' }"

vuejs if/else: check if element exsists else add a element

I have a v-for like so:
<p> User Responses(s):</p>
<template v-for="item in UserResponses">
<ol v-if="item.some_condition==item._is_true">
<li :name="item.id + '__UR'"> [[ item.some_variable_to_render ] ]] </li>
</ol>
</template>
Works great. However, what I would like to do is say No user responses when the some_condition_is_true fails, but if I add an v-else the message (No user responses) will be printed in every loop, which is not desired ofcourse. How does one solve this problem?
To this end, I wondered if I could test if the element item.id + '__UR'" is present and if not add an element with text sayingNo user responses`
I am not sure however that this is the correct way to go about this.
EDIT
Just to reiterate: using v-if before v-for is not an option since the object being iterated on is a nesed JSON which then is filtered through some vuejs filters, so yea, this is not an option.
note that boolVar corresponds to your actual object key that contains the bools
let model.flag = true
function foo(val) => {
model.flag = val
return val
}
<p> User Responses(s):</p>
<template>
<div v-if="model.flag">
<div v-for="item in UserResponses" >
<ol v-if="foo(item.Boolvar)" >
<li :name="item.id + '__UR'"> [[ item.some_variable_to_render ] ]] </li>
</ol>
</div>
</div>
<h1 v-else>No user responses 😢</h1>
</template>
It is possible you may be able to implement this using CSS:
ol + .no-responses {
display: none;
}
There's a full example below but the idea is simply to hide the message if it comes after an <ol>. Tweak the selector to reflect your real use case.
I have tried to keep the template in my example exactly the same as the original code, with the addition of the relevant message and a couple of buttons for demo purposes.
new Vue({
el: '#app',
delimiters: ['[[', ']]'],
data () {
return {
UserResponses: []
}
},
methods: {
add () {
this.UserResponses.push(
{ some_condition: 3, _is_true: 3, id: 3, some_variable_to_render: 'A' },
{ some_condition: 3, _is_true: 4, id: 4, some_variable_to_render: 'B' },
{ some_condition: 4, _is_true: 4, id: 5, some_variable_to_render: 'C' },
{ some_condition: 5, _is_true: 5, id: 6, some_variable_to_render: 'D' }
)
},
clear () {
this.UserResponses.splice(0)
}
}
})
ol + .no-responses {
display: none;
}
<script src="https://unpkg.com/vue#2.6.11/dist/vue.js"></script>
<div id="app">
<p>User Responses(s):</p>
<template v-for="item in UserResponses">
<ol v-if="item.some_condition==item._is_true">
<li :name="item.id + '__UR'"> [[ item.some_variable_to_render ]] </li>
</ol>
</template>
<p class="no-responses">No user responses</p>
<button #click="add">Add values</button>
<button #click="clear">Clear values</button>
</div>

Vue.js - Resetting the filterBy results

https://jsfiddle.net/72tnpa0o/
<div id="filter-by-example">
<label>Cool</label><input type="checkbox" v-model="cool">
<label>notCool</label><input type="checkbox" v-model="notCool">
<ul>
<li v-for="user in users | filterBy cool in 'cool' | filterBy notCool in 'notCool'">
{{ user.name }}
</li>
</ul>
</div>`
new Vue({
el: '#filter-by-example',
data: {
name: '',
users: [
{
name: 'Bruce',
cool: true,
notCool: false
},
{
name: 'Chuck',
cool: false,
notCool: true
},
{
name: 'Jackie',
cool: true,
notCool: false
}
]
}
})
I have an example up at the fiddle link above. I'm able to sort via the input checkboxes, but unchecking the filters doesn't reset to the original results.
Does anyone know how after unchecking a filter button to get the full array to render?
The main issue here is that the logic of the selector is wrong: as it stands now, if you deselect both checkboxes, you will get the items that are both cool and notCool. The reason it's working in the beginning is because there's an error in your code (open the console, you will see errors there): both cool and notCool are undefined at the beginning.
So first you need to outline what do you want. Once you've done this, you can use a function to filter instead of using the out-of-the-box one, something like this:
<li v-for="user in users | filterBy filterCool">
{{ user.name }}
</li>
And filterCool should be like:
filterCool: function (element, i, list) {
if (this.showCool && element.cool) return true;
if (this.showNotCool && element.notCool) return true;
if (!this.showNotCool && !this.showCool) return true;
return false;
}
Not a perfect solution right now, but a good way to start. Check this: https://jsfiddle.net/72tnpa0o/5/
The filter does not apply if the :false-value checkbox is empty
Try change this:
<input type="checkbox" v-model="cool">
<input type="checkbox" v-model="notCool">
by
<input type="checkbox" :false-value="" v-model="cool">
<input type="checkbox" :false-value="" v-model="notCool">
Also make sure to add an key for your list items so that vue can track each item and show relevant data (https://v2.vuejs.org/v2/guide/list.html#key).
Vue 2:
<li
v-for="(user, key) in users | filterBy filterCool"
:key="key"
>
{{ user.name }}
</li>
Vue 1:
<li
v-for="user in users | filterBy filterCool"
track-by="$index"
>
{{ user.name }}
</li>

Categories