iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

v-for

v-for renders a template once per item in an array, object, or numeric range. Always bind a :key — it’s how Vue tracks which DOM nodes to reuse vs recreate.

Lists, objects, ranges, key strategies

EXAMPLE
<script setup>
import { ref, computed } from 'vue';

const todos = ref([
    { id: 1, title: 'Ship feature', done: false },
    { id: 2, title: 'Write tests',  done: true  },
    { id: 3, title: 'Update docs',  done: false },
]);

const filter = ref('all');

const filtered = computed(() => {
    if (filter.value === 'done')   return todos.value.filter(t => t.done);
    if (filter.value === 'todo')   return todos.value.filter(t => !t.done);
    return todos.value;
});
</script>

<template>
    <!-- 1) Loop an array -->
    <ul>
        <li v-for="todo in filtered" :key="todo.id">
            <input type="checkbox" v-model="todo.done" />
            <span :class="{ done: todo.done }">{{ todo.title }}</span>
        </li>
    </ul>

    <!-- 2) With index — (item, index) shape -->
    <ol>
        <li v-for="(todo, i) in filtered" :key="todo.id">
            {{ i + 1 }}. {{ todo.title }}
        </li>
    </ol>

    <!-- 3) Object iteration — (value, key, index) -->
    <dl>
        <template v-for="(value, key) in headers" :key="key">
            <dt>{{ key }}</dt>
            <dd>{{ value }}</dd>
        </template>
    </dl>

    <!-- 4) Numeric range — 1..N -->
    <span v-for="n in 5" :key="n" class="star">★</span>

    <!-- 5) Nested loop — composite key -->
    <ul v-for="section in sections" :key="section.id">
        <h3>{{ section.title }}</h3>
        <li v-for="item in section.items" :key="\`${section.id}-${item.id}\`">
            {{ item.label }}
        </li>
    </ul>

    <!-- 6) v-for on a <template> (no extra wrapper element) -->
    <template v-for="row in rows" :key="row.id">
        <tr>
            <td>{{ row.name }}</td>
            <td>{{ row.value }}</td>
        </tr>
    </template>

    <!-- 7) v-for + v-if — Vue 3: use a computed or wrap with template -->
    <!-- BAD: v-if on the same element as v-for is no longer allowed -->
    <template v-for="todo in todos" :key="todo.id">
        <li v-if="!todo.done">{{ todo.title }}</li>
    </template>
    <!-- Or pre-filter with a computed (preferred) -->

    <!-- 8) Reuse a Component in the list -->
    <TodoItem v-for="t in todos" :key="t.id" :todo="t" @done="markDone" />
</template>

<style scoped>
.done { text-decoration: line-through; opacity: 0.6; }
</style>

<!-- 9) Performance + key strategy -->
<!-- The key MUST be stable + unique across the list (NOT the index).
     Using the index causes Vue to reuse DOM nodes for the wrong items
     when the list is reordered, filtered, or items are inserted in the middle. -->

<!-- ❌ -->
<li v-for="(t, i) in todos" :key="i">…</li>

<!-- ✅ -->
<li v-for="t in todos" :key="t.id">…</li>

<!-- 10) Reactivity caveats — mutating arrays
     These trigger updates (wrapped by Vue's reactivity):
         push / pop / shift / unshift / splice / sort / reverse
     These DON'T (you must reassign):
         array[i] = newValue        →  use array.splice(i, 1, newValue)
         array.length = N           →  use array.splice(N) -->

Why it matters

The :key is what lets Vue identify a node across renders. Use a stable, unique business key (id); never use the array index unless the list is genuinely static.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
<li v-for="item in items" :key="item.id">{{ item.name }}</li>
Try it Yourself »

Exercise

Loop over items.

<li v-for="item in items" ="item.id">

Discussion

Loading…