In Vue, v-for loops are something that every project will use at some point or another. They allow you to write for loops in your template code and create elements iteratively.
v-for
is most commonly used to render items in a list, but can also be used to iterate over an object's properties or even a set range.
When iterating over an array, Vue v-for loops look like this.
<template>
<div v-for="product in products">
{{ product.name }}
</div>
</template>
In this article, we’ll be covering some ways to make your v-for
loops more precise, predictable, and efficient.
Accessing index in v-for
In addition to accessing our values, we can also keep track of the index of each item.
To do this, we have to add an `index`` value after our item. It’s simple, and is useful for things like pagination, displaying the index of a list, showing rankings, etc.
Like other indices in programming, index starts at 0.
<script setup lang="ts">
import { ref } from 'vue'
const products = ref([
{id: 0, name: 'shirt'},
{id: 1, name: 'jacket'},
{id: 2, name: 'shoes'}
])
</script>
<template>
<div
v-for='(product, index) in products'
:key='product.id'
>
#{{ index + 1}} - {{ product.name }}
</div>
</template>
Use key
in your Vue v-for loops
A common Vue best practice is to use :key
in your v-for
loops.
key
is a special attribute that lets us give hints for Vue's rendering system to identify specific virtual nodes.
If we don't provide a unique key
, Vue will try to minimize element movement when updating the DOM.
For example, if we want to change the order of our list, instead of reordering the DOM elements - Vue will make edits to the elements in-place to reflect the data change. This is called an in-place patch.
If we have a unique key reference for each element (usually some unique id), Vue's rendering system will reorder elements instead of performing in-place patches.
Do we really need key
though?
Two cases where a missing key
can cause problems in our v-for
are when our v-for
has:
- stateful DOM elements (like form inputs)
- when rendering components
Like we saw, Vue's default rendering avoids moving DOM elements, so if we had a child component or form input, it doesn't move with our product when we reorder our list. Instead, each product.name
gets patched with the new product - but the input stays in place.
To fix this, we need to tell Vue that each li
generated by our v-for
is tied to a specific product and all of its content (including the input) should move when our products get order.
You guessed it - that's where we can use key
.
The same thing applies if our iterated content contains a child component. Without a key, it will stay in place when we reorder our list.
But don't use index as the key!
The purpose of specifying a key
is for Vue to link each item in our list to its corresponding vnode. Then, when the orders of our keys change - the orders of the elements will as well.
If we use index
as our key, the order of the keys will never change - since index always increases sequentially. This acts the same as not declaring a key since the index will be the same at each element's place.
Should you always use key?
It's a best practice to add the key
attribute to your v-for
loops whenever you can. By always using it, you can avoid debugging the cases where it's essential (components and stateful DOM elements).
Also, cases where we want list movement to be animated using <TransitionGroup>
requires a key
since we will be moving our elements.
In cases where your iterated content is simple, key
won't always impact the functionality of your v-for
, but it's still recommended to get in the habit of always adding one. Then, you can make exceptions on a case-by-case basis.
Using v-for to loop over a range
While most of the time v-for is used to loop over arrays or objects, there are definitely cases where we might just want to iterate a certain number of times.
For example, let’s say we are creating a pagination system for an online store and we only want to show 10 products per page. Using a variable to track our current page number, we could handle pagination like this.
<ul>
<li v-for="index in 10" :key="index">{{ products[page * 10 + index] }}</li>
</ul>
Avoid use v-if in loops
A common mistake is improperly using v-if
to filter the data that we are looping over with our v-for.
Although this seems intuitive, it can cause performance issues because Vue prioritizes the v-for over the v-if directive.
So, your component will loop through every element in our list and THEN check the v-if
conditional to see if it should render.
If you use v-if with v-for, you’ll be iterating through every item of your array no matter what your conditional is.
<!--BAD-->
<ul>
<li v-for="product in products" :key="product._id" v-if="product.onSale">
{{ product.name }}
</li>
</ul>
So what’s the issue?
Let’s say our products array has 1000 items, but only 3 of them are on sale and should be rendered.
Every time our component re-renders, Vue will loop over all 1000 products even if the 3 products on sale didn't change at all.
Let’s look at two alternatives to this anti-pattern.
Use computed properties or a method instead
We can filter our data before iterating over it in our template. There are two very similar ways to do this:
- Using a computed property
- Using a filtering method
The way you choose to do this is up to you, so let’s just cover both real quick.
First, we have to set up a computed property. To get the same functionality as our earlier v-if
, the code would look like this.
<template>
<ul>
<li v-for="products in productsOnSale" :key="product._id">
{{ product.name }}
</li>
</ul>
</template>
<script>
export default {
data() {
return {
products: [],
}
},
computed: {
productsOnSale: function () {
return this.products.filter((product) => product.onSale)
},
},
}
</script>
This has a few benefits:
- Our data property will only be re-evaluated when a dependency changes
- Our template will only loop over the products on sale, instead of every single product
The code for the method is pretty much the same, but using a method changes how we access the values in our template. However, if we need to pass in a variable into our filter (let's say productsOnSale
is used multiple times with different price points), methods are the way to go.
<template>
<ul>
<li v-for="products in productsOnSale(50))" :key="product._id">
{{ product.name }}
</li>
</ul>
</template>
<script>
export default {
data() {
return {
products: [],
}
},
methods: {
productsOnSale(maxPrice) {
return this.products.filter(
(product) => product.onSale && product.price < maxPrice
)
},
},
}
</script>
Or surround your loop with a wrapper element
Another time you may want to join v-for with v-if
is when deciding whether or not to render a list at all.
For example, what if we only want to render our products list when a user is logged in.
<!--BAD-->
<ul>
<li
v-for="product in products"
:key="product._id"
v-if="isLoggedIn"
>
> {{ product.name }}
</li>
</ul>
What’s wrong with this?
Same as before. Our Vue template will prioritize the v-for
– so it will loop over every element and check the v-if
.
So even if our list renders nothing, we’re going to loop over thousands of elements.
The easy solution here is to move our v-if
statement.
<!--GOOD-->
<ul v-if="isLoggedIn">
<!-- Much better -->
<li v-for="product in products" :key="product._id">{{ product.name }}</li>
</ul>
This is much better because if isLoggedIn
is false – we’re not going to have to iterate at all.
Iterating over an object
So far, we’ve only really looked at using v-for to iterate through an array. But we can just as easily iterate over an object’s key-value pairs.
Similar to accessing an element’s index, we have to add another value to our loop. If we loop over an object with a single argument, we’ll be looping over all of the items.
If we add another argument, we’ll get the items AND keys. And if we add a third, we can also access the index of the v-for loop.
With everything, it would look like this.Let’s just say we want to loop over each property in our products.
<ul>
<li v-for='(products, index) in products' :key='product._id' >
<span v-for='(item, key, index) in product' :key='key'>
{{ item }}
</span>
</li>
</ul>
Conclusion
Hopefully, this quick article taught you some of the best practices about using Vue’s v-for directive.
What other tips do you have? Let me know in the replies!!
Happy coding 🙂