Files
stack/packages/mosaic/framework/skills/vue-best-practices/reference/no-v-if-with-v-for.md
T
fargo 1a822493ba format: apply repo prettier (3.8.1) to the folded skills tree
963 markdown files reformatted with the repository's pinned prettier so
pnpm format:check covers the folded tree like every other repo file.

The formatter's embedded-language pass also normalized code fences
(TS semicolons, closed HTML tags in examples, lowercased CSS hex colors,
one renumbered list that skipped an index). Alphanumeric token deltas vs
the fold commit were audited file-by-file; all are formatter-equivalent
markup normalizations plus the four sanitized skills.
2026-08-19 14:37:17 -05:00

3.8 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Never Use v-if and v-for on the Same Element HIGH Causes confusing precedence issues and Vue 2 to 3 migration bugs capability
vue3
v-if
v-for
conditional-rendering
list-rendering
eslint

Never Use v-if and v-for on the Same Element

Impact: HIGH - Using v-if and v-for on the same element creates ambiguous precedence that differs between Vue 2 and Vue 3. In Vue 2, v-for had higher precedence; in Vue 3, v-if has higher precedence. This breaking change causes subtle bugs during migration and makes code intent unclear.

The ESLint rule vue/no-use-v-if-with-v-for enforces this best practice.

Task Checklist

  • Never place v-if and v-for on the same element
  • For filtering list items: use a computed property that filters the array
  • For hiding entire list: wrap with <template v-if> around the v-for
  • Enable eslint-plugin-vue rule vue/no-use-v-if-with-v-for

Incorrect:

<!-- WRONG: v-if and v-for on same element - ambiguous precedence -->
<template>
  <!-- Intent: show only active users -->
  <li v-for="user in users" v-if="user.isActive" :key="user.id">{{ user.name }}</li>
</template>
<!-- WRONG: Hiding entire list conditionally -->
<template>
  <li v-for="user in users" v-if="shouldShowList" :key="user.id">{{ user.name }}</li>
</template>
<!-- WRONG: Vue 3 precedence issue -->
<template>
  <!-- In Vue 3, v-if runs FIRST, so 'user' is undefined! -->
  <li v-for="user in users" v-if="user.isActive" :key="user.id">{{ user.name }}</li>
  <!-- Error: Cannot read property 'isActive' of undefined -->
</template>

Correct:

<!-- CORRECT: Filter with computed property -->
<template>
  <li v-for="user in activeUsers" :key="user.id">{{ user.name }}</li>
</template>

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

  const props = defineProps(['users']);

  const activeUsers = computed(() => props.users.filter((user) => user.isActive));
</script>
<!-- CORRECT: Wrap with <template v-if> for conditional list -->
<template>
  <template v-if="shouldShowList">
    <li v-for="user in users" :key="user.id">{{ user.name }}</li>
  </template>
</template>
<!-- CORRECT: v-if inside the loop (per-item condition) -->
<template>
  <ul>
    <template v-for="user in users" :key="user.id">
      <li v-if="user.isActive">{{ user.name }}</li>
    </template>
  </ul>
</template>

Vue 2 vs Vue 3 Precedence Change

// Vue 2: v-for evaluated first
// <li v-for="user in users" v-if="user.isActive">
// Equivalent to: users.forEach(user => { if (user.isActive) render(user) })

// Vue 3: v-if evaluated first
// <li v-for="user in users" v-if="user.isActive">
// Equivalent to: if (user.isActive) users.forEach(user => render(user))
// Problem: 'user' doesn't exist yet when v-if runs!

Why Computed Properties Are Better

// Benefits of filtering via computed:
// 1. Clear separation of concerns (logic vs template)
// 2. Cached - only recalculates when dependencies change
// 3. Reusable - can be used elsewhere in component
// 4. Testable - can unit test the filtering logic
// 5. No ambiguity about intent

const activeUsers = computed(() => users.value.filter((u) => u.isActive));

// Can add more complex filtering
const filteredUsers = computed(() =>
  users.value
    .filter((u) => u.isActive)
    .filter((u) => u.role === selectedRole.value)
    .sort((a, b) => a.name.localeCompare(b.name)),
);

Reference