What is a v-for loop?
In Vue, we have access to a directive called v-for.
This allows you to create loops and output dynamic content within your template.
Let's take a look at a typical example.
Here we have a standard <ul>
with some list items. Let's take this unordered list and convert it to use a v-for
loop.
<template>
<ul>
<li>John</li>
<li>James</li>
<li>Jerry</li>
</ul>
</template>
<ul>
<li>John</li>
<li>James</li>
<li>Jerry</li>
</ul>
First, we need to convert the list of items into an array. On the <li>,
we need to add a v-for
directive.
This syntax may look familiar if you've worked with a for loop. A v-for
requires the format of people in peopleList
where peopleList
is our source of data and people
is our param for the array element.
We must also add a key
attribute, which we will cover next within this post.
<template>
<ul>
<li v-for="person in peopleList" :key="person">{{ person }}</li>
</ul>
</template>
<script setup>
const peopleList = ["John", "James", "Jerry"];
</script>
<ul>
<li>John</li>
<li>James</li>
<li>Jerry</li>
</ul>
These snippets will produce the same result, but using the v-for
loop makes our code more dynamic!
Now that we have a basic understanding of what a v-for
loop is let's jump into the tips!
1. Use key attribute
Earlier, we briefly covered using a key
on a v-for
loop. Using a key
is one of the most common best practices when it comes to using a v-for
loop.
So what is a key?
A key
is a special attribute used to help Vue keep track of items within the virtual DOM when comparing a new list of nodes against a old list.
What should the key be?
The key
binding expects primitive values (strings & numbers).
When defining a key, you need to ensure it's unique. A common practice is using an id.
Since an id
is usually always unique, this is by far the best choice for the value of your key.
If an items within the loop share the same key value, this will result in render errors.
Is adding a key required?
The simple answer is no. A key
is not technically required. However, it is recommended as a best practice to get in the habit of adding one.
Adding a key will give you more control of how the DOM gets manipulated, especially when lists are dynamic and could change.
If you choose not to define a key, Vue will default to using an "In place patch strategy" to keep track & make updates to the item.
In place patch strategy is efficient, but only suitable when your list render output does not rely on child component state or temporary DOM state. We an example of this in the next section.
Key vs no key
Without a key,
Vue will use the in-place patch strategy. When you attempt to reorder / update the list, instead of moving the DOM elements to match the order of the items, Vue will patch each element in it's current position to reflect what should be rendered at that index.
This can be seen in the component below if you add some quantities to the grocery store cart and attempt to reorder the items.
You'll notice that the names of the grocery items get updated, but the qualities remain in the same position.
<template>
<ul>
<li v-for="item in food">
<p>{{ item.name }}:</p>
<input type="text" placeholder="enter amount" />
</li>
</ul>
<button @click="reorderItems">Reorder Items</button>
</template>
<script setup>
import { ref } from "vue";
const food = ref([
{ id: 1, name: "Grapes" },
{ id: 2, name: "Strawberries" },
{ id: 3, name: "Apples" },
]);
const reorderItems = () => {
food.value = [food.value[2], food.value[0], food.value[1]];
};
</script>
Without Key
Here we have a mock grocery store cart, with several items. Enter in some quantities for each item. Then attempt to reorder the list by clicking on the button.
Grapes:
Strawberries:
Apples:
*With no key added, you will see the names update, but the quantity remains the same.
Now, let's add a key
to the li
and see how the list reacts when reordered.
Since we added a key
, the entire DOM element will be moved instead of updating the items in their current position.
<template>
<ul>
<li v-for="item in food" :key="item.id">
<p>{{ item.name }}:</p>
<input type="text" placeholder="enter amount" />
</li>
</ul>
<button @click="reorderItems">Reorder Items</button>
</template>
<script setup>
import { ref } from "vue";
const food = ref([
{ id: 1, name: "Grapes" },
{ id: 2, name: "Strawberries" },
{ id: 3, name: "Apples" },
]);
const reorderItems = () => {
food.value = [food.value[2], food.value[0], food.value[1]];
};
</script>
With Key
Here we have a mock grocery store cart, with several items. Enter in some quantities for each item. Then attempt to reorder the list by clicking on the button.
Grapes:
Strawberries:
Apples:
*With a key added, you will see the name and the quantity update together.
2. Access the index
Within a v-for
loop, not only can we access the value of each item we are iterating through, but we can also get access to the element's position with an index.
To access, we need to wrap the parameter within ()
and pass a second param of index.
The index can be used for a variety of different things, such as:
- Pagination.
- Displaying list numbers.
- Reference to a specific item within the array that will be updated or removed.
<ul>
<li v-for="(person, index) in filteredPeople" :key="person.id">
{{index}} : {{ person }}
</li>
</ul>
3. Avoid using the index for key
As mentioned earlier, adding a key
to a v-for
loop is important.
A common way i see a key
begin assigned is through the index
. Although this might seem like a straightforward workaround, you should avoid doing this if possible.
Sometimes using the index
is acceptable, as long as that list is static. Meaning you won't be doing any manipulation to it.
If the list is dynamic, you want to avoid using the index
and opt for a more unique key,
such as an id.
<ul>
<li v-for="(person, index) in filteredPeople" :key="index">
{{index}} : {{ person }}
</li>
</ul>
Otherwise, if you choose to use the index, you might experience some undesired behavior when doing things such as:
- Updating list items
- Moving items within the list
- Adding new items
4. V-for loop with an object
Often we use a v-for loop to iterate through an array. However, we can also iterate through an object to output all of its properties.
The structure is the same as an array. Where property
is the source of data (param), and then obj
is the object we will iterate through.
<template>
<ul>
<li v-for="property in obj" :key="property">{{ property }}</li>
</ul>
</template>
<script setup>
const obj = ref({
name: "John",
age: 24,
email: "john@johnkomarnicki.com",
});
</script>
<ul class="flex flex-col">
<li class="m-0">John</li>
<li class="m-0">24</li>
<li class="m-0">john@johnkomarnicki.com</li>
</ul>
With an object, we might want to output each property's key
(also called the property name
). We can do this by wrapping the original property
param we defined in ()
and then passing a second param, key,
to obtain the key of that property.
<template>
<ul>
<li v-for="(property, key) in obj" :key="property">{{key}} : {{ property }}</li>
</ul>
</template>
<script setup>
const obj = ref({
name: "John",
age: 24,
email: "john@johnkomarnicki.com",
});
</script>
<ul class="flex flex-col">
<li class="m-0">name: John</li>
<li class="m-0">age: 24</li>
<li class="m-0">email: john@johnkomarnicki.com</li>
</ul>
5. V-for loop with range (number)
In addition to iterating thought arrays and objects, we can use a v-for
loop with a range.
This can be helpful in many different scenarios. One example where this would be useful is with pagination.
In this example, there is an array of people
to list out. To keep track of the current page, we'll create a new ref, currentPage,
and set it equal to 0.
Within the template, we will define a v-for
loop, and for the param, we will call it i
. For this pagination, we only want to display ten people per page, so for the second param, we will define 10
. Lastly, we will add a key and set it equal to the param of i
'.
Then inside each li
using mustache syntax, we will output each person. Since we only want to display 10 per page, we need to define people[currentPage * 10 + i]
.
<template>
<ul>
<li v-for="i in 10" :key="i">{{ people[currentPage * 10 + i] }}</li>
</ul>
</template>
<script setup>
import { ref } from "vue";
const currentPage = ref(1);
const people = ref([
"John",
"Jack",
"Jim",
"Tim",
"Betty",
"Scott",
"Andrew",
"Tyler",
"Harold",
"Frank",
"Seth",
"Dave",
]);
</script>
<ul class="flex flex-col">
<li class="m-0">John</li>
<li class="m-0">Jack</li>
<li class="m-0">Jim</li>
<li class="m-0">Tim</li>
<li class="m-0">Betty</li>
<li class="m-0">Scott</li>
<li class="m-0">Andrew</li>
<li class="m-0">Tyler</li>
<li class="m-0">Harold</li>
<li class="m-0">Frank</li>
</ul>
6. V-for loop with template tag
So far, we have only used a v-for
loop on a single element.
<ul>
<li v-for="person in people" :key="person.id">{{ person }}</li>
</ul>
If we want to render out a block of elements, we can use a v-for
loop on a template.
<template v-for="person in people" :key="person.id">
<p>{{person.name}}</p>
<p>{{person.id}}</p>
</template>
7. No v-if with v-for
When creating v-for
loops, it might be very tempting to add a v-if
directive to the element.
Although tempting, you should never do this. When these two exist on the same element, the v-if
directive will have a higher priority than the v-for.
Let's take a look at an example.
In this scenario, since the v-if
has a higher priority than the v-for,
it will attempt to make the comparison for the v-if
directive before iterating through the data, resulting in an error.
<ul>
<li v-for="person in people" v-if="person.age > 24" :key="person.id">
{{ person }}
</li>
</ul>
An easy solution would be to wrap the li
within a template
and have the v-for
live with the template.
Then, within the li,
we can make the comparison.
<ul>
<template v-for="person in peopleList" :key="person.id">
<li v-if="person.age > 24">{{ person }}</li>
</template>
</ul>
8. Use computed properties for filtered data
In the previous tip, we saw how we could use a v-if
and a v-for
together. Although this is fine, there is a much better and more efficient way to show filtered data using a computed property.
However, I usually recommend using a v-if
and v-for
to determine if the list should be rendered, not to show specific items within the list.
<ul v-if="userLoggedIn">
<li v-for="person in peopleList" :key="person.id">{{ person }}</li>
</ul>
To properly filter data more efficiently, we can create a new computed property called filteredPeople.
This will return all the people within the array with an age greater than 24.
Instead of iterating through the people
array, we can use the filteredPeople,
which will already have the data filtered, and we no longer have to use a v-if
directive within this loop.
Inside the browser, we will only see people aged 24 and over.
<ul>
<li v-for="person in filteredPeople" :key="person.id">{{ person }}</li>
</ul>
<script setup>
import { computed, ref } from "vue";
const people = ref([
{ id: uid(), age: 23, name: "John" },
{ id: uid(), age: 27, name: "James" },
{ id: uid(), age: 31, name: "Jerry" },
]);
const filteredPeople = () => {
return people.value.age > 24;
};
</script>
<ul class="flex flex-col">
<li class="m-0">James</li>
<li class="m-0">Jerry</li>
</ul>
Conclusion
Overall, v-for
loops are essential to your Vue apps. Hopefully, you found this article helpful, and it can help you start writing better v-for
loops.
Have any other best practices or topics you'd want me to cover? Feel free to reach out at john@johnkomarnicki.com.
Happy coding!