Javascript is required
·
6 min read

Unlocking the Power of v-for Loops in Vue With These Useful Tips

Unlocking the Power of v-for Loops in Vue With These Useful Tips Image

Looping through arrays and objects is a common task in Vue applications. The v-for directive is the perfect tool for this job. It is mighty, and you can use it in many different ways. In this article, I will show you some valuable tips and tricks to get the most out of the v-for directive.

Use correct delimiter

The v-for directive supports two different delimiters: in and of. The in delimiter is the default one.

I prefer to use the of delimiter for arrays because it is closer to JavaScript's syntax for iterators like in the for...of loop:

Component.vue
1<script setup lang="ts">
2import { ref } from 'vue'
3
4const items = ref([
5  { id: 'a-1', name: 'Apple' },
6  { id: 'b-1', name: 'Mango' },
7  { id: 'c-1', name: 'Banana' },
8])
9</script>
10
11<template>
12  <ul>
13    <li v-for="item of items">
14      {{ item.name }}
15    </li>
16  </ul>
17</template>

If I want to loop through an object, I use the in delimiter because it is closer to JavaScript's syntax for iterating over object properties:

Component.vue
1<script setup>
2import { ref } from 'vue'
3
4const myObject = ref({
5  title: 'How to do lists in Vue',
6  author: 'Jane Doe',
7  publishedAt: '2016-04-10',
8})
9</script>
10
11<template>
12  <ul>
13    <li v-for="(value, key) in myObject">
14      Key: "{{ key }}", Value: "{{ value }}"
15    </li>
16  </ul>
17</template>

Destructuring objects

It's possible to destructure the current item in the loop, which is useful if you want to access the current item's properties directly:

Component.vue
1<script setup>
2import { ref } from 'vue'
3
4const items = ref([
5  { id: 'a-1', name: 'Apple' },
6  { id: 'b-1', name: 'Mango' },
7  { id: 'c-1', name: 'Banana' },
8])
9</script>
10
11<template>
12  <ul>
13    <li v-for="{ name } in items">
14      Title: {{ name }}
15    </li>
16  </ul>
17</template>

Iterating over numbers

You can also use the v-for directive to iterate over numbers. This is useful if you want to render a list of elements with a specific number of items. For example, you can use it to render a list of 10 items:

Component.vue
1<template>
2  <ul>
3    <li v-for="number in 10">{{ number }}</li>
4  </ul>
5</template>

Accessing index

Sometimes, you need to access the current item's index in the loop. You can do this by using the second argument of the v-for directive:

Component.vue
1<script setup lang="ts">
2import { ref } from 'vue'
3
4const items = ref([
5  { id: 'a-1', name: 'Apple' },
6  { id: 'b-1', name: 'Mango' },
7  { id: 'c-1', name: 'Banana' },
8])
9</script>
10
11<template>
12  <ul>
13    <li v-for="(item, index) in items" :key="item.id">
14      #{{ index + 1 }} - {{ item.name }}
15    </li>
16  </ul>
17</template>

Avoid v-if in v-for loops

Using v-if and v-for on the same element is not recommended due to implicit precedence.

In this case, you should wrap the v-for loop in a <template> element and use v-if on the <template> element instead, which is also more explicit:

1<template>
2  <li v-for="item in items" v-if="!item.isComplete">
3    {{ item.name }}
4  </li>
5</template>
1<template>
2  <template v-for="item in items">
3    <li v-if="!item.isComplete">
4      {{ item.name }}
5    </li>
6  </template>
7</template>

Use key attribute

It is recommended to provide a key attribute with v-for whenever possible. key is a special attribute that lets us give hints for Vue's rendering system to identify specific virtual nodes.

Let's assume we have a list of todos and want to add a new todo. We can use the splice() method to insert a new todo at a specific index. If we don't provide a key attribute, Vue will be unable to identify the new todo and will not update the UI correctly.

First, let's take a look at the TodoItem component:

TodoItem.vue
1<script setup lang="ts">
2import { ref, onMounted } from 'vue'
3
4interface Props {
5  todo: {
6    id: number
7    name: string
8    completed: boolean
9  }
10}
11const props = defineProps<Props>()
12
13const todoName = ref('')
14const localTodoBageColor = computed(() => {
15  return props.todo.name !== todoName.value ? 'red' : 'gray'
16})
17
18onMounted(() => {
19  todoName.value = props.todo.name
20})
21</script>
22
23<template>
24  <div class="flex items-center gap-4">
25    <div class="rounded-lg border border-gray-300 p-2 dark:border-gray-500">
26      <UCheckbox color="white" size="lg" :model-value="props.todo.completed" :label="todo.name" />
27    </div>
28    <div class="flex items-center gap-2">
29      <span class="text-sm text-gray-300 dark:text-gray-500">Local todo name:</span>
30      <UBadge variant="soft" :color="localTodoBageColor">{{ todoName }}</UBadge>
31    </div>
32  </div>
33</template>

This simple component renders a todo item passed as a prop and has a local state to store the todo name, which is updated if the component is mounted.

Let's take a look at an interactive example without a key attribute. We have a list of todos and want to insert a new todo at a specific index, try it yourself by clicking the Insert new Todo button:

Local todo name:
Local todo name:
Local todo name:
ForLoopWithoutKey.vue
1<script setup lang="ts">
2import { ref } from 'vue'
3
4const todos = ref([
5{ id: 0, name: 'Buy milk', completed: false },
6{ id: 1, name: 'Hit the gym', completed: false },
7{ id: 2, name: 'Read a book', completed: true },
8])
9
10const addNewTodo = () => {
11todos.value.splice(2, 0, { id: 3, name: 'Learn Vue', completed: false })
12}
13</script>
14
15<template>
16<UCard>
17  <div class="flex flex-col gap-4">
18    <UButton class="w-32" @click="addNewTodo">Insert new Todo</UButton>
19    <TodoItem v-for="todo in todos" :todo="todo">
20      <UBadge variant="soft" color="white" size="lg">{{ todo.name }}</UBadge>
21      <UCheckbox color="white" :model-value="todo.completed" />
22    </TodoItem>
23  </div>
24</UCard>
25</template>

The new Learn Vue todo is inserted at the correct index, but the local todo name is not updated. This is because Vue is not accurately tracking the index as being new. Thus, the component will never re-mount, so our localTodoName will never get updated. Instead, the value of localTodoName will be that of the previous todo at that index.

You might have guessed it, we can simply fix that problem by providing a key attribute to our v-for loop:

Local todo name:
Local todo name:
Local todo name:
ForLoopWithoutKey.vue
1<script setup lang="ts">
2import { ref } from 'vue'
3
4const todos = ref([
5{ id: 0, name: 'Buy milk', completed: false },
6{ id: 1, name: 'Hit the gym', completed: false },
7{ id: 2, name: 'Read a book', completed: true },
8])
9
10const addNewTodo = () => {
11todos.value.splice(2, 0, { id: 3, name: 'Learn Vue', completed: false })
12}
13</script>
14
15<template>
16<UCard>
17  <div class="flex flex-col gap-4">
18    <UButton class="w-32" @click="addNewTodo">Insert new Todo</UButton>
19    <TodoItem v-for="todo in todos" :key="todo.id" :todo="todo">
20      <UBadge variant="soft" color="white" size="lg">{{ todo.name }}</UBadge>
21      <UCheckbox color="white" :model-value="todo.completed" />
22    </TodoItem>
23  </div>
24</UCard>
25</template>

Array Change Detection Caveats

You must carefully use non-mutating methods on your array in v-for loops, such as filter() or map(). These methods return a new array, which means that Vue cannot detect changes to the array.

If you want to use these methods, you need to assign the new array to the original array:

Component.vue
1<script setup lang="ts">
2import { ref } from 'vue'
3
4const items = ref([
5  { id: 'a-1', name: 'Apple' },
6  { id: 'b-1', name: 'Mango' },
7  { id: 'c-1', name: 'Banana' },
8])
9
10// ⚠️ items.value.filter((item) => item.id.startsWith('a'))
11items.value = items.value.filter((item) => item.id.startsWith('a'))
12</script>
13
14<template>
15  <ul>
16    <li v-for="item in items">
17      {{ item.name }}
18    </li>
19  </ul>
20</template>

If you use mutation methods like push(), pop(), shift(), unshift(), splice(), sort(), or reverse(), you don't need to assign the new array to the original array because these methods mutate the original array.

Conclusion

I hope you learned something new about the v-for directive in this article. It's one of the most powerful directives in Vue, and you can use it in many different ways. If you want to learn more about the v-for directive, check out the official documentation.

If you liked this article, follow me on X to get notified about my new blog posts and more content.

Alternatively (or additionally), you can subscribe to my weekly Vue newsletter:

I will never share any of your personal data. You can unsubscribe at any time.

If you found this article helpful.You will love these ones as well.
Simpler Two-Way Binding in Vue With defineModel Image

Simpler Two-Way Binding in Vue With defineModel

Focus & Code Diff in Nuxt Content Code Blocks Image

Focus & Code Diff in Nuxt Content Code Blocks

Lazy Load Vue Component When It Becomes Visible Image

Lazy Load Vue Component When It Becomes Visible

A Comprehensive Guide to Data Fetching in Nuxt 3 Image

A Comprehensive Guide to Data Fetching in Nuxt 3