Vuejs removing element from array can not remove it completely - javascript

When I try to run the following code, and I remove one item from the array, the item is not removed completely(there are other checkboxes part of each row which are not removed) I have added a :key="index" and doesn't help it.
Nevertheless when I have changed the :key="index" to :key="item" it works, but then the problem is I get the warning [Vue warn]: Avoid using non-primitive value as key, use string/number value instead
<template>
<div>
<filters-list-item v-for="(item, index) in items" :key="index" v-on:deleteItem="deleteItem(index)" :items="items" :item="item" :filterButtonSetting="filterButtonSetting" class="pb-3 pt-3 space-line"></m-filters-list-item>
<div class="pt-3">
<button class="btn" #click="add()">
add
</button>
</div>
</div>
</template>
<script>
import FiltersListItem from './FiltersListItem';
export default {
name: 'FiltersList',
components: {
FiltersListItem
},
props: {
items: Array,
filterButtonSetting: Object
},
methods: {
add() {
this.items.push({});
},
deleteItem(index) {
this.$delete(this.items, index);
},
}
};

Using the index is fine as long as you are not interacting with any of the elements in the loop.
But, if you are, then it is recommended not to do this.
You should use another unique item's identifier, maybe providing one from the backend.

Related

Vuejs: How to make this if else as Boolean?

I have a component with buttons that show some elements (v-autocomplete) when we click on them, but I should make it with a Boolean to simplify the code, how can I do that?
Because actually, it adds the index of the item in one array in showCompetence state, but I just would like a Boolean on each index Basically at the "openCompetence" function in "Methods".
import { mapGetters, mapActions } from "vuex";
export default {
name: "SkillCvCard",
data() {
return {
selectedCompetence: []
}
},
updated() {
this.addSelectSkill(this.userCompetences.competences.list);
console.log(this.selectedSkills)
},
props: {
userCompetences: {
type: Array
},
showCompetence: {
type: Array
}
},
computed: {
...mapGetters(["selectedSkills"]),
console: () => console,
},
methods: {
...mapActions(['addSelectSkill']),
openCompetence(index) {
if (this.showCompetence.includes(index)) {
console.log("close")
this.showCompetence.splice(this.showCompetence.indexOf(index), 1)
} else {
this.showCompetence.push(index)
console.log("open")
}
console.log(this.showCompetence)
}
}
about the template I will just add the minimum I hope it will be ok:
The first is where we click, to launch the "openCompetence" function.
<div v-for="(competences, index) of userCompetences.competences" :key="index">
{{ competences.category }} <v-btn #click="openCompetence(index)"> Add </v-btn>
</div>
the rest is a v-container with a v-for including multiple v-autocomplete, but the most important inside is the v-if:
<div class="skill-field" v-for="(skill, index) of userCompetences.competences" :key="index">
<template>
<v-flex md12 sm12 xs12 v-if="skill.list.length>0">
<v-autocomplete
v-if="showCompetence.includes(index)"
v-model="userCompetences.competences.list"
:items="skill.list"
chips
hide-selected
:label="skill.category"
item-text="name"
item-value="name"
multiple
>
</v-autocomplete>
</v-flex>
</template>
</div>
Normally, you would use a computed property to solve a problem like this. However, because you are inside of a v-for, a computed property is a little more difficult since you can't evaluate it in the context of each value of the v-for's index.
There are two common options:
Do exactly what you are doing. It's actually no less efficient since it will not re-evaluate unless data it depends on changes.
Add a computed called something like competencesDisplayState that returns an array of booleans that matches the ordering of the userCompetences.competences array in the v-for. Then your v-if can become something like:
v-if="competencesDisplayState[index]"
I normally just opt for the first approach as it is simpler and is easier to read and maintain.
If you want to go route 2, here's some code for the competencesDisplayState computed:
competencesDisplayState: () => {
var result = [];
for (var index=0;index < this.userCompetences.competences.length;++index) {
result(this.showCompetence.includes(index));
}
return result;
}

Vue, v-bind: Passing data as a property

I am trying to pass data as a property in a component. v-bind is required, but i can't seem to make it work. Error is one line 3 in the HTML, the v-bind directive doesn't work and returns an error: "Custom elements in iteration require a "v-bind:key" directive"
<script>
export default {
name: "ChatLog",
props: {
},
components: {ChatMessage},
data() {
return {
messages: [
{
message: "Hey",
user: "James"
},
{
message: "Hola",
user: "Jimmy"
},
]
}
}
}
</script>
<template>
<div class="log">
<ChatMessage v-for="(message) in messages" v-bind:message="message"> </ChatMessage>
</div>
</template>
You may try this (The key is required in a loop):
<template>
<div class="log">
<ChatMessage
v-for="(message, key) in messages"
v-bind:message="message"
v-bind:key="key"
></ChatMessage>
</div>
</template>
Btw, you may use shorthand alternative for v-bind like this:
<ChatMessage v-for="(message, key) in messages" :message="message" :key="key" />
Also, if message is unique, then you may use the message as the value for key like this:
<ChatMessage v-for="message in messages" :message="message" :key="message" />
Note: Read more about the key here.
You need to specify a key for the given element in the for loop.
<ChatMessage v-for="(message) in messages" v-bind:message="message" :key="message"> </ChatMessage>

Linking a text field in a child component to a parent component's props in VueJS

I have a child component sending data via an event to a parent component in VueJS. From the parent component, I am routing the data (or trying to route the data...) to a sibling of the child and create new components with the data sent from the child.
I use a dictionary to group the data for various reasons, then push the dictionary into an array. A v-for loop loops thru the array and populates the previously mentioned new components with data found in that array. I probably don't need to do it this way, but that's how I'm doing it. I am open to alternatives.
Anyway, it doesn't work great. So far I'm only able to get one of the three strings I need to show up where I want it to. I'll explain more after I post the code.
Already tried:
A dozen different versions of the code, including creating a simple v-for in a list to do the job, and various versions with/without a dictionary or array.
In my research for the problem I've gone through the VueJS docs, Googled a few things, and found nothing.
In App.vue (I tried to remove all the irrelevant stuff):
<template>
<div id="app">
<img alt="Vue logo" src="./assets/logo.png">
<TweetDeck v-on:messageFromTweetDeck="msgReceived($event)"/>
<!-- <ul>
<li v-for="(tweet, index) in tweets" :key="index">{{ tweet }}</li>
</ul>-->
<TwitterMsg v-for="(tweet, index) in tweets" :key="index"
:name="tweet.name" :handle="tweet.handle" tsp=3 :msg="tweet.tweet" />
<TwitterMsg name="aaa" handle='aaa'
tsp=50 msg="hey this is a message on twitter"/>
<input type="text" v-model="placeholderText"/>
</div>
</template>
<script>
import TwitterMsg from './components/TwitterMsg.vue'
import TweetDeck from './components/TweetDeck.vue'
export default {
name: 'app',
components: {
TwitterMsg,
TweetDeck
},
data: function() {
return {
tweets: [],
message: "",
placeholderText: ""
}
},
methods: {
msgReceived(theTweet, name, handle) {
this.tweets.push({tweet: theTweet, name: name, handle: handle})
}
}
}
</script>
And in TweetDeck.vue:
<template>
<div>
<input type='text' v-model="yourName">
<input type='text' v-model="yourHandle">
<input type='text' v-model="yourTweet"/>
<button type='button' #click="sendTweet()">Tweet</button>
</div>
</template>
<script>
export default {
name: "TweetDeck",
data: function() {
return {
yourName: "Your name here",
yourHandle: "Your twitter handle",
yourTweet: "What's going on?"
}
},
methods: {
sendTweet() {
this.$emit('messageFromTweetDeck', this.yourTweet, this.yourName, this.yourHandle);
}
}
}
</script>
You can also see the mostly unimportant TwitterMsg.vue here (I am trying to copy Twitter for learning purposes:
<template>
<div>
<h4>{{ name }}</h4>
<span>#{{ handle }}</span>
<span> {{ tsp }}</span> <!-- Time Since Posting = tsp -->
<span>{{ msg }}</span>
<img src='../assets/twit_reply.png'/><span>1</span>
<img src="../assets/twit_retweet.png"/><span>2</span>
<img src="../assets/twit_fave.png"/><span>3</span>
</div>
</template>
<script>
export default {
name: "TwitterMsg",
props: {
name: String,
handle: String,
tsp: String,
msg: String
}
}
</script>
<style>
img {
width: 30px;
height: 30px;
}
</style>
Expected result:
The code populates a new TwitterMsg component with appropriate name, handle and message data each time I click the "Tweet" button.
Actual results:
My code fails to help the name and handle strings make it from the input text box in TweetDeck.vue all the way to their home in TwitterMsg.vue.
I will say that this.yourTweet in TweetDeck.vue DOES manage to make it all the way to its destination, which is good -- though it makes me wonder why the other two pieces of data didn't follow suite.
Totally lost. Also just in my first month of VueJS so it's pretty good that I can even make one string appear where I want it to. \o/
First, you need to remove the $event parameter
<TweetDeck v-on:messageFromTweetDeck="msgReceived"/>
Second, you can optimize the data format passed to the parent component:
sendTweet() {
this.$emit("messageFromTweetDeck",
{ tweet: this.yourTweet, name: this.yourName, handle: this.yourHandle }
);
}
And then modify your msgReceived method:
msgReceived(childData) {
this.tweets.push(childData);
}
Link: codesandbox
Hope to help you:)

Element Ui component is not rerendering on vue component prop change

I have a parent component and multiple child components, which use the same prop. This prop is an array of keys for a dropdown menu in element.js.
When the children render the first time, they contain no data. However, once the keys from arrive using vuefire the children get the dropdown menu items. However, the element dropdown menu is not rerendered as it should have been.
However using the vue dev tools, I can see that the dropdown menu entries have been passed down as a key. When vue does a hot reload, because of a file change, the keys will load.
Once the entries are loaded, I can select the entry and everything works as expected.
I also had the same results using the vuetify dropdown and the HTML dropdown. Both have the same issue.
parent
<template>
<div class="setup">
<h1>Setup</h1>
<div class="selectIngredients" v-for="number in 6">
<setupSelection :bottle="number" :ingredients="options" />
</div>
</div>
</template>
<script>
import {db} from "#/firebaseConfig"
import setupSelection from '#/components/setupSelection';
export default {
components: {
setupSelection,
},
firestore: {
options: db.collection('ingredients'),
},
};
</script>
child
<template>
<div class="ingredientSelector">
<h3>Select for Pump <span>{{bottle}}</span></h3>
<el-select v-model="selected" clearable placeholder="Select" >
<el-option
v-for="ingredient in ingredients"
v-bind:key="ingredient.text"
v-bind:label="ingredient.text"
v-bind:value="ingredient">
</el-option>
</el-select>
<!-- <v-select
v-model="selected"
:items="ingredients"
label="Select a favorite activity or create a new one"
></v-select> -->
<!-- <select v-model="selected" v-for="ingredient in ingredients">
<option :value="ingredient.value">{{ingredient.text}}</option>
</select> -->
</div>
</template>
<script>
import {db} from "#/firebaseConfig";
export default {
props: {
ingredients: { required: true },
bottle: { type: Number, required: true },
},
data() {
return {
selected: ''
}
},
},
};
</script>
I expected the dropdown menu to update once the client received them.
Thank you!
I haven't used Vuefire myself but I read the following in the documentation:
Make sure to create any property added to firestore in data as well
https://github.com/vuejs/vuefire/tree/master/packages/vuefire#firestore-option
Similar advice is given here:
https://vuefire.vuejs.org/vuefire/binding-subscriptions.html#declarative-binding
In your example you don't have options in the parent's data. This would, presumably, leave it non-reactive, leading to the symptoms you describe.
Use a data property for your items, and set them after the options are loaded.
data() {
return {
options: []
}
},
created() {
db.collection('ingredients').then(data=> this.options = data}
}
The promise returned from db.collection('ingredients') is not reactive.
Even better approach would be to set options: null, and show a loading indicator until it is an array.

VueJS Show element on child component one at a time

So I have several components <server-header>. It has the following HTML:
<span #click="$parent.changeOrder(column, $event)">
<slot></slot>
<i v-show="sortActive" class="order-arrow" :class="sort"></i>
</span>
These components are inserted in another component <server-list>. These will be the headers, that when clicked, will order some lists. My objective is to only show one arrow icon at a time.
E.g.: If I click on the first header, the arrow appears on that one. If I click on the second header, the arrow from the first header hides and the one on the second header appears.
This would be simple to do with jQuery for example, but I'm kind of lost on how to do this with VueJS.
Don't call parent functions directly. That is an anti pattern. Instead use 2-way data binding with sync and this is much easier. Rough example below:
// server-list.vue
data() {
return {
selected: undefined
}
}
<server-header v-for="(header, idx) in headers" :header="header" :selected.sync="selected" :idx="idx"></server-header
Then in the child, we drop #click="$parent.changeOrder(column, $event)" in favor of:
#click="$emit('update:selected', idx)"
Make sure server-header has this prop expected:
props: ['idx', 'header', 'selected']
Then make sure we compare the idx to our header index:
<i v-show="selected === index"
I assume you're rendering the <server-header> components with a v-for directive, looping a server_headers property in your <server-list> component data property.
The way i go with this is adding a prop to the <server-header> component like selected, then I add a selected property to each of the server_headers objects. Then i render the icon (<i>) with v-show if the correspondent selected property is true.
Like this:
server-list component
Vue.component('server-list', {
data: () => ({
server_headers: [{key: value, selected: true}, {key:value, selected:false}]
}),
template: `
<div>
<server-header v-for="header of server_headers" :v-key="somekey" :selected="header.selected"></server_header>
</div>
`
}
Vue.component('server-header', {
props: ['selected'],
template: `
<span #click="$parent.changeOrder(column, $event)">
<slot></slot>
<i v-show="selected" class="order-arrow" :class="sort"></i>
</span>
`
})
Then, if i click on the arrow I would unset all the selected properties, and set the one i clicked. I hope this helps!!!!

Categories

Resources