When exactly are keys useful in Vue? - vue.js

I still don't quite get the use of keys even after reading through the docs but I understand that it has to do with keeping each item unique. I've been solely adding a key when using a v-for and only to the outer most parent (not the children of the v-for. Are there any other situations when keys should be used?
<div v-for="(person,index) in people" :key="index>
<div class="name-label>
<img :src="person.img/>
<div> {{ person.name }} </div>
</div>
</div>

The key directive alongside v-for, is used to uniquely identify each parent element rendered on the v-for operation.
The same happens in Reactjs:
{elements.map((value, index) => {
return <li key={index}>{value}</li>
})}
The key directive in Vuejs is also used to force Vuejs to re-render an element that contains it every time :key receives a new value.

As #firmino-changani indicated, other uses of key is if you want Vue to force a re-render or replacement of an element or component. See key special attribute documentation:
It can also be used to force replacement of an element/component
instead of reusing it. This can be useful when you want to:
Properly trigger lifecycle hooks of a component
Trigger transitions For example:
<transition>
<span :key="text">{{ text }}</span>
</transition>
When text changes, the <span> will always be replaced instead of patched, so a transition will be triggered.
Another example, lets say you have a dialog popup to add new data and another component that needs updating once the new data was entered via the popup. This second component might have logic inside lifecycle hooks like onMounted etc. Using a :key you can force re-rending this component once the dialog popup is saved e.g.
<template>
<page>
<add-data-popup #save="myKey++" />
<show-data :key"myKey" />
</page>
</template>

From the vueJs docs around Keys:
The key special attribute is primarily used as a hint for Vue’s virtual DOM algorithm to identify VNodes when diffing the new list of nodes against the old list.
To simply put it, if a div is rendered via a For-loop the div will be identical to each other div. When a user clicks on one of these Div's and you instantiate a #click function, how will vueJs know what differentiates each of these divs? How will vue know how to reorder these div's in the correct order if you remove a middle div?
VueJs uses the key to know how to reorder the DOM when an action is taken on one of these objects in your For loop.
Here is an article fully explaining about vueJs keys, and more details on the VueJS reactivity engine which is good to know about: https://www.telerik.com/blogs/in-vue-when-do-i-actually-need-the-key-attribute-and-why

Related

Vue: communicating parameters from app to component and back, using computed properties

Sorry for the beginner question (I'm fairly sure this will be a duplicate, but I actually can't figure out what terms to use in order to find it). I just started with Vue.
I am just getting started with Vue, and following this course (https://www.vuemastery.com/courses/intro-to-vue-js/communicating-events). In this problem, there is (and here I don't know the term, so I'm going to go with...) app-level data parameter called cart. cart is an array which holds the id of each item a user has added to cart.
The problem tells us to add a button to remove items from the cart.
I ran into problems trying to create a computed property, which would allow me to hide the "remove" button in the event the selected item is not in the cart (eg. `
Communicating data from the app-level (the cart array), to the component-level (to a computed property in the product component), so that I could use something like :hidden="!inCart" on the "Remove from Cart" button, which is itself defined in the component. inCart would be a computed value here.
Communicating the selected product from the component to the app-level, computing inCart at the app level, then using the computed value at the component-level.
Either way, I can't seem to figure out how to do this in the way I would want to, which would look something like how v-bind operates. Namely, I think I may be able to hack together a solution using methods (which I believe have to be triggered by certain events), but I don't understand how I might go about this using built-in functionality such that the value of inCart is dynamically auto-computed.
Maybe there would be an answer to this in the next few courses, but I don't see us covering that in the intro material. Sorry for the neophyte question. Thank you in advance.
In Vue the way you communicate "state" from higher-level objects to lower-level objects is through props.
So, assuming your app looks something like...
<MyApp>
<MyShoppingPageWithItems>
<MyItem></MyItem>
<MyItem></MyItem>
<MyShoppingPageWithItems>
</MyApp>
You need to pass the cart object down as a prop.
So in your MyShoppingPageWithItems template, you'll have something like...
<template>
<div>
<MyItem v-for="item in items" :item=item :cart="cart"?
</MyItem>
</div>
</template>
And in your item template...
<template>
<div>
<div>
{{item.name}}
</div>
<div v-if="cart.includes(item.id)">
Remove button or whatever
</div>
</div>
</template>
Not that the .includes() method is a native JavaScript method, which you can read more about here.
Edit
To reference a prop in a computed property (or anywhere else in a Vue component), just refer to this.propName, as demonstrated here in the Vue docs.
So, if you want to create a computed property, you can do the following:
<template>
<div>
<div>
{{item.name}}
</div>
<div v-if="isInCart">
Remove button or whatever
</div>
</div>
</template>
<script>
export default {
props: ['cart', 'item'],
computed: {
isInCart() {
return this.cart.includes(this.item.id)
}
}
}
</script>
Note that the formula is the exact same as above, but just includes this. for cart and item. In templates, the this. is implied when referring to props, data, and computed properties.

Should I turn this code into own component if I am not sure whether I'd need to use it elsewhere?

I have a page in which I loop through an array of objects and render a <p> element for each element like this:
<p #click='selectCity(index)' v-for='(location, index) in locations'>
{{ location.city }}, {{ location.country }}
</p>
This is just an example and in the future I might have to render more stuff than just a <p> element per object in the array. Should I make a component out of this and just v-for the component by passing the item to it?
Even though I've used Vue for about 3 weeks, I'm still a little fuzzy when should I make out a component of something and when should I not.
I wouldn't. You use a component if you need some component feature like local state, a computed for each item, or lifecycle hooks. Or, obviously, if you're using the block of code in multiple places.
I don't see any reason here to move to a component. When your requirements change, maybe you will need one.

We also need to provide each component with a "key" vuejs

<div id="app-7">
<ol>
<!--
Now we provide each todo-item with the todo object
it's representing, so that its content can be dynamic.
We also need to provide each component with a "key"
-->
<todo-item
v-for="item in groceryList"
v-bind:todo="item"
v-bind:key="item.id">
</todo-item>
</ol>
</div>
My question why We also need to provide each component with a "key" where any example that can help me to understand please
Vue uses the key attribute to "track" each node rendered by a v-for loop, for performance purpose.
You can find more information here https://v2.vuejs.org/v2/guide/list.html#key
When Vue is updating a list of elements rendered with v-for, by default it uses an “in-place patch” strategy. If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will patch each element in-place and make sure it reflects what should be rendered at that particular index. This is similar to the behavior of track-by="$index" in Vue 1.x.
that's from the docs

VueJs - Deleting a File-input component from a list

I'm still pretty new to vue.js.
I recently created some vue components to tidy up my html. The components are different inputs like text and checkbox. They update the parents data using $emit('input', val). And make sure that the components are up-to-date using props and watch() on those props.
Now there is a problem with using watch() on <input type="file />, since you cannot set the input value like you can on other input types.
Using this example:
https://jsfiddle.net/minde281/nyu73dz6/25/
I have a list of, in this case, items on a shoppinglist. You can add an image for each item.
The image is loaded and added to the list. This works fine.
To get my problem:
add an image to item1
delete that item by clicking the X-button
The result is now that item2 will have that image on the <input type="file" />. The preview works as expected since this can be set through script and therefore use watch(). But somehow vue removes the wrong part of the html markup causing the last one to be removed.
Is there a different way to solve this problem? Or is there a way to tell vue to remove the correct part of the html markup?
-Minde
Vue tries to reuse the component and thats why the selected file is in the second input after deleting the component.
To prevent that you should use the key binding like this:
<li v-for="(item, index) in items" :key="item.name">
Now the hole component gets removed and everything should work as expected.

Vue component not updating after parent state change

I have Vue component which receives json data from props, after this render child components using v-for and pass this data as prop. Everything works fine until i try to remove one item from this list, when i remove it, it removes element incorrectly. In vue devtools i see that parent receives data correctly but not renders it properly. can anyone help me?
here is my code: https://gist.github.com/giokaxo/3d291b9b7b8ef97f81dc83799c430302
Use "key" attribute when rendering elements using v-for, for example:
<p v-for="(product, index) in order.products" :key="i">..</p>
The relevant documentation is here:
You can directly use v-for on a custom component, like any normal
element:
<my-component v-for="item in items" :key="item.id"></my-component>
In 2.2.0+, when using v-for with a component, a key is now required.
However, this won’t automatically
pass any data to the component, because components have isolated
scopes of their own. In order to pass the iterated data into the
component, we should also use props:
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id">
</my-component>
The reason for not automatically injecting item into the component is because that makes the component
tightly coupled to how v-for works. Being explicit about where its
data comes from makes the component reusable in other situations.
And here:
When Vue is updating a list of elements rendered with v-for, it by
default uses an “in-place patch” strategy. If the order of the data
items has changed, instead of moving the DOM elements to match the
order of the items, Vue will simply patch each element in-place and
make sure it reflects what should be rendered at that particular
index.
...
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. An ideal value for key would be the unique id
of each item.