AcademyVue
Conditional Rendering and v-for Lists
v-if versus v-show is a cost trade-off, not a style choice, and v-for needs a stable :key for the same reason React needs one on map().
3 min readUpdated 3 exercises
v-if destroys and recreates; v-show only toggles CSS
Both hide an element when their expression is falsy. What they do underneath is different, and the difference has a real cost:
<template>
<!-- v-if: the element and its component instance are torn down and
rebuilt every time the condition flips -->
<ExpensivePanel v-if="isOpen" />
<!-- v-show: the element stays mounted, only display:none toggles -->
<ExpensivePanel v-show="isOpen" />
</template>v-if is the right default: it does not pay to render something the condition usually keeps hidden, and unmounting genuinely destroys the component's state, refs and effects — useful when "closed" should mean "reset". v-show earns its place when something toggles often (a tab panel, a search box someone types into) and the cost of tearing it down and rebuilding it every keystroke — including whatever local state or focus it was holding — outweighs the memory of keeping it around. Toggling frequency, not visual similarity, decides between them.
Chaining follows the same rules as if/else if/else in JavaScript, and the elements have to be adjacent with nothing between them:
<template>
<p v-if="status === 'online'">● Online</p>
<p v-else-if="status === 'away'">● Away</p>
<p v-else>● Offline</p>
</template>v-for needs a key for the same reason a React list does
v-for iterates an array or object and renders one element per item. Without a stable :key, Vue's patch algorithm falls back to matching elements by position — exactly the bug class that keying by array index reproduces in React:
<template>
<!-- Bug: keyed by position. Insert an item at the front and every
existing element gets re-matched to the wrong data. -->
<li v-for="(todo, index) in todos" :key="index">{{ todo.text }}</li>
<!-- Correct: keyed by something that identifies the item itself -->
<li v-for="todo in todos" :key="todo.id">{{ todo.text }}</li>
</template>The failure is invisible on a list that only ever appends to the end — index and id happen to agree there. It shows up the moment an item is inserted, removed or reordered anywhere but the tail: a form input rendered per-row now holds the wrong row's draft value, because Vue reused the DOM node at that position rather than moving the node that actually owns that data.
Deriving a filtered list keeps the source of truth singular
A "hide completed" checkbox is not new data — it is a view over the same todos array, and computed is what keeps that relationship explicit instead of duplicated:
<script setup lang="ts">
import { ref, computed } from 'vue'
const todos = ref([/* ... */])
const hideCompleted = ref(false)
const visibleTodos = computed(() =>
hideCompleted.value ? todos.value.filter((t) => !t.done) : todos.value,
)
</script>The alternative — a second ref holding the filtered array, kept in sync by hand on every mutation — is exactly the kind of derived state that drifts from its source the first time someone adds a new way to mutate todos and forgets to update the copy.
What interviewers check
Whether "v-if or v-show" gets an answer grounded in toggle frequency and state-reset semantics rather than a coin flip, and whether :key="index" gets flagged on sight — it is one of the most common review comments on a real Vue pull request.
Now practice it
1 quiz · 2 drillsReading this page is the cheap half. These are the exercises that make you use it.
- Conditional RenderingChain v-if/v-else-if/v-else correctly, then swap a frequently-toggled panel from v-if to v-show.DrillSolve · EasyDrillSolve · Easy
- List RenderingDerive a filtered list with computed and fix a :key="index" bug — the exact mistake this page names.DrillSolve · EasyDrillSolve · Easy
- Options API · Options API Deep-diveOptions API question set that still exercises the same rendering and directive fundamentals.QuizTake · MediumQuizTake · Medium
Read next
Template Syntax: Interpolation and Bindings
What {{ }} can and cannot render, why every dynamic attribute needs v-bind, and how :class and :style differ from a plain HTML string.
3 min
Ref vs Reactive: Vue's State Primitives
ref wraps any value behind a .value getter/setter; reactive proxies an object directly. The trade-offs explain why most Vue code reaches for ref by default.
2 min