Lists: v-for and key
The v-for directive repeats an element for every value of an array. Instead of a loop that creates elements with createElement, you describe one element, and Vue renders as many as there are records in the data.
Rendering an array
<!-- src/App.vue -->
<script setup>
import { ref } from 'vue'
const todos = ref([
{ id: 1, text: 'Buy bread', done: true },
{ id: 2, text: 'Call Anna', done: false },
{ id: 3, text: 'Write a report', done: false },
])
</script>
<template>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
</li>
</ul>
</template>The page shows a list of three tasks. todo in todos works like for...of: the todo variable takes each task in turn and is available inside li.
The key attribute
:key is a unique key of the element. Vue uses it to know which DOM element belongs to which record when the list changes: when a task is removed from the middle, it removes exactly that li instead of re-rendering every row. Use the id from the data as the key — a string or number that never repeats.
Index
A second variable in parentheses gives the element's position:
<template>
<ul>
<li v-for="(todo, index) in todos" :key="todo.id">
{{ index + 1 }}. {{ todo.text }}
</li>
</ul>
</template>The rows get numbers: "1. Buy bread", "2. Call Anna" and so on. Indexes start at zero, so one is added.
Changing the list
The list on the page updates when the array changes:
<script setup>
import { ref } from 'vue'
const todos = ref([
{ id: 1, text: 'Buy bread', done: false },
{ id: 2, text: 'Call Anna', done: false },
])
function removeTodo(id) {
todos.value = todos.value.filter((todo) => todo.id !== id)
}
</script>
<template>
<ul>
<li v-for="todo in todos" :key="todo.id">
{{ todo.text }}
<button @click="removeTodo(todo.id)">Delete</button>
</li>
</ul>
</template>The "Delete" button removes its own task: the handler receives the id from the current loop iteration.
Other sources
| Syntax | What is looped over |
|---|---|
v-for="todo in todos" | Array items |
v-for="n in 5" | Numbers from 1 to 5 |
v-for="(value, key) in user" | Values and keys of an object |
A filtered list is rendered through computed from the computed properties lesson: v-for="todo in visibleTodos".
Common mistake
Putting v-if and v-for on the same element:
<li v-for="todo in todos" v-if="!todo.done" :key="todo.id">
{{ todo.text }}
</li>v-if runs before v-for, so the todo variable does not exist in it yet — Vue reports an error. Filter the list in computed, or move v-for to a template wrapper and keep v-if on li.