Skip to main content
Vue.js for Beginners to Advanced
CHAPTER 10 Beginner

Conditional Rendering and Lists

Updated: May 18, 2026
5 min read

# CHAPTER 10

Conditional Rendering and Lists

1. Chapter Introduction

Showing or hiding UI elements based on state, and rendering dynamic lists of data, are the two most common tasks in any frontend application. Vue's v-if/v-show and v-for directives handle both elegantly.

2. Learning Objectives

  • Use v-if, v-else-if, v-else for conditional rendering.
  • Use v-show for toggling visibility.
  • Render dynamic lists with v-for.
  • Optimize lists with proper :key usage.
  • Build a filtered product listing.

3. Conditional Rendering Deep Dive

vue
123456789101112131415161718192021222324252627
<script setup>
import { ref } from &#039;vue'
const user = ref({ loggedIn: true, role: &#039;admin', verified: false })
const loading = ref(false)
const error = ref(null)
const data = ref([1, 2, 3])
</script>

<template>
  <!-- State-based rendering pattern -->
  <div v-if="loading">⏳ Loading...</div>
  <div v-else-if="error">❌ Error: {{ error }}</div>
  <div v-else-if="data.length === 0">📭 No data found.</div>
  <div v-else>
    <!-- Show data -->
    <div v-for="item in data" :key="item">{{ item }}</div>
  </div>

  <!-- Role-based rendering -->
  <template v-if="user.loggedIn">
    <nav v-if="user.role === &#039;admin'">Admin Nav</nav>
    <nav v-else-if="user.role === &#039;editor'">Editor Nav</nav>
    <nav v-else>User Nav</nav>
    <span v-if="!user.verified" class="warning">⚠️ Please verify your email</span>
  </template>
  <div v-else>Please log in</div>
</template>

4. List Rendering Deep Dive

vue
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
<script setup>
import { ref, computed } from &#039;vue'

const products = ref([
  { id: 1, name: &#039;MacBook Pro', price: 1999, category: 'Laptops', rating: 4.9 },
  { id: 2, name: &#039;iPhone 15 Pro', price: 999, category: 'Phones', rating: 4.8 },
  { id: 3, name: &#039;AirPods Pro', price: 249, category: 'Audio', rating: 4.7 },
  { id: 4, name: &#039;iPad Air', price: 599, category: 'Tablets', rating: 4.6 },
  { id: 5, name: &#039;Apple Watch', price: 399, category: 'Wearables', rating: 4.5 },
])

const selectedCategory = ref(&#039;All')
const sortBy = ref(&#039;rating')
const searchQuery = ref(&#039;')

const categories = computed(() => [&#039;All', ...new Set(products.value.map(p => p.category))])

const filteredProducts = computed(() => {
  let result = products.value

  // Filter by category
  if (selectedCategory.value !== &#039;All') {
    result = result.filter(p => p.category === selectedCategory.value)
  }

  // Filter by search
  if (searchQuery.value) {
    result = result.filter(p =>
      p.name.toLowerCase().includes(searchQuery.value.toLowerCase())
    )
  }

  // Sort
  return [...result].sort((a, b) => {
    if (sortBy.value === &#039;price-asc') return a.price - b.price
    if (sortBy.value === &#039;price-desc') return b.price - a.price
    if (sortBy.value === &#039;rating') return b.rating - a.rating
    return a.name.localeCompare(b.name)
  })
})
</script>

<template>
  <div class="product-listing">
    <!-- Search and filters -->
    <div class="controls">
      <input v-model="searchQuery" placeholder="Search products..." />
      <select v-model="sortBy">
        <option value="rating">Best Rated</option>
        <option value="price-asc">Price: Low to High</option>
        <option value="price-desc">Price: High to Low</option>
        <option value="name">Name A-Z</option>
      </select>
    </div>

    <!-- Category tabs -->
    <div class="tabs">
      <button
        v-for="cat in categories"
        :key="cat"
        @click="selectedCategory = cat"
        :class="{ active: selectedCategory === cat }"
      >{{ cat }}</button>
    </div>

    <!-- Results count -->
    <p class="count">{{ filteredProducts.length }} products found</p>

    <!-- Product grid -->
    <div class="grid">
      <div v-for="product in filteredProducts" :key="product.id" class="product-card">
        <div class="product-category">{{ product.category }}</div>
        <h3>{{ product.name }}</h3>
        <div class="rating">★ {{ product.rating }}</div>
        <div class="price">${{ product.price }}</div>
        <button class="add-cart-btn">Add to Cart</button>
      </div>
    </div>

    <!-- Empty state -->
    <div v-if="filteredProducts.length === 0" class="empty">
      <p>😔 No products match your search. Try different filters.</p>
      <button @click="searchQuery = &#039;'; selectedCategory = 'All'">Reset Filters</button>
    </div>
  </div>
</template>

<style scoped>
.controls { display: flex; gap: 1rem; margin-bottom: 1rem; }
.controls input, .controls select { padding: .5rem .75rem; border: 1px solid #e2e8f0; border-radius: 8px; }
.tabs { display: flex; gap: .5rem; margin-bottom: 1rem; flex-wrap: wrap; }
.tabs button { padding: .35rem .9rem; border: 1px solid #e2e8f0; border-radius: 99px; cursor: pointer; background: white; }
.tabs button.active { background: #6366f1; color: white; border-color: #6366f1; }
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 1rem; }
.product-card { border: 1px solid #e2e8f0; border-radius: 12px; padding: 1.25rem; background: white; }
.product-category { font-size: .75rem; color: #6366f1; font-weight: 600; margin-bottom: .5rem; }
h3 { margin: 0 0 .5rem; font-size: 1rem; }
.rating { color: #f59e0b; font-size: .85rem; margin-bottom: .5rem; }
.price { font-size: 1.25rem; font-weight: 800; color: #1e293b; margin-bottom: .75rem; }
.add-cart-btn { width: 100%; padding: .6rem; background: #6366f1; color: white; border: none; border-radius: 8px; cursor: pointer; font-weight: 600; }
.empty { text-align: center; padding: 3rem; color: #64748b; }
.count { color: #64748b; font-size: .875rem; margin-bottom: 1rem; }
</style>

5. Common Mistakes

  • Using index as :key for mutable lists: v-for="(item, i) in items" :key="i" causes incorrect DOM recycling when items are reordered or removed. Use item.id.
  • v-if and v-for on the same element: Never. Use a <template> wrapper.

6. MCQs

Question 1

v-for :key best practice?

Question 2

What renders when no v-for items match a filter?

Question 3

v-show vs v-if for performance with frequent toggles?

Question 4

v-for with a computed filtered list is better because?

Question 5

computed caches based on?

Question 6

v-for iterating an object gives?

Question 7

v-for="n in 5" iterates?

Question 8

Computed filtered + sorted list vs method?

Question 9

<template v-for> renders?

Question 10

v-else requires?

7. Interview Questions

  • Q: Why is using index as :key in v-for a bad practice?
  • Q: How would you implement a filterable, sortable product list in Vue?

8. Summary

Conditional rendering with v-if/v-show and list rendering with v-for + :key are the two most-used Vue features. Computed properties power efficient filter and sort operations. The product listing project demonstrates how these combine with reactivity to create fully interactive, zero-JavaScript-DOM-manipulation UIs.

9. Next Chapter Recommendation

In Chapter 11: Forms and Input Handling, we build production-quality forms with validation, custom inputs, file uploads, and reactive form state management.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·