Skip to the solution
EasyFrontend10 min read

How do you render lists with v-for?

880 views

The problem

Question presented to candidate: "We get an array of orders from an API and need to show them as a list, with only the open ones in a second list. How do you render lists in Vue with v-for, and what are the rules to get it right?"

What a strong answer should cover:

  • Syntax: v-for="item in items", with the index as (item, index) in items; of works as well as in; destructuring is allowed.
  • v-for also iterates objects ((value, key, index)), integer ranges (n in 10 starts at 1) and any iterable such as a Map or Set.
  • Always give each item a stable, unique :key (an id, not the index) so Vue can reuse and move the right DOM nodes and component state.
  • <template v-for> repeats a group of elements without a wrapper; in Vue 3 its :key goes on the <template>.
  • Do not put v-if and v-for on the same element: in Vue 3 v-if runs first and cannot see the loop variable. Filter with a computed property instead.
  • Array changes through mutation methods (push, splice, sort, reverse) and replacement (filter, map) are all reactive.

Clarifying questions expected:

  • "Do items have a stable unique id I can use as the key?"
  • "Roughly how many items? Thousands might need virtual scrolling."

Code / implementation expected: Yes. A keyed list plus a computed filtered list.

  • vue
  • v-for
  • lists
  • templates

Try it yourself

Sketch your approach before you read the solution. That is what the interview tests.

Stuck? Get a hint.

A short nudge toward the approach. It will not give the answer away.

Solution

The solution is hidden so you can try first

10 min read Β· 10 sections

Read the code

Vuev-for over arrays, objects, a range, a Map and a template group, with push, reverse and splice
<script setup>
import { ref, computed } from "vue";

const todos = ref([
  { id: 1, text: "Write docs", done: false },
  { id: 2, text: "Fix bug", done: true },
  { id: 3, text: "Review PR", done: false },
]);
const user = ref({ name: "Ada", role: "admin" });
const tags = new Map([["vue", 3], ["css", 1]]);

// Filter with a computed, never with v-if on the same element as v-for.
const open = computed(() => todos.value.filter((t) => !t.done));

let nextId = 4;
const add = () => todos.value.push({ id: nextId++, text: "New task", done: false });
const removeFirst = () => todos.value.splice(0, 1);
const reverse = () => todos.value.reverse();
</script>

<template>
  <!-- 1. Array with index; :key gives each row a stable identity -->
  <ol class="all">
    <li v-for="(todo, index) in todos" :key="todo.id">{{ index }}:{{ todo.text }}</li>
  </ol>

  <!-- 2. Filtered list via computed -->
  <ul class="open">
    <li v-for="todo in open" :key="todo.id">{{ todo.text }}</li>
  </ul>

  <!-- 3. Object: value, key, index -->
  <p class="obj"><span v-for="(value, key, i) in user" :key="key">[{{ i }} {{ key }}={{ value }}]</span></p>

  <!-- 4. A range starts at 1, not 0 -->
  <p class="range"><span v-for="n in 5" :key="n">{{ n }};</span></p>

  <!-- 5. Any iterable works: a Map gives [key, value] pairs -->
  <p class="map"><span v-for="[tag, count] of tags" :key="tag">{{ tag }}x{{ count }};</span></p>

  <!-- 6. template v-for repeats a group without a wrapper element -->
  <dl class="dl">
    <template v-for="todo in todos" :key="todo.id">
      <dt>{{ todo.text }}</dt>
      <dd>{{ todo.done ? "done" : "open" }}</dd>
    </template>
  </dl>

  <button class="add" @click="add">add</button>
  <button class="rm" @click="removeFirst">remove first</button>
  <button class="rev" @click="reverse">reverse</button>
</template>

Discussion

Other approaches, follow-ups and how it went in real interviews.

Sign in to join the discussion.

No comments yet. Share how you would answer it.

Question 7 of 92 in Vue.js

Keep your momentum going.

Mark this question solved to track your progress, then take the next one while it is fresh.

All Vue.js questions
Up nextEasy

What changed from Vue 2 to Vue 3, and what does Vue 2 end of life mean?

Continue