Because the precedence between them changed from Vue 2 to Vue 3, and putting them together creates ambiguous code regardless of version.
In Vue 2, v-for runs first. In Vue 3, v-if runs first. This means the same code behaves differently:
vue
Open in Vue Playground<!-- Vue 2: iterates all users, then filters by isActive (works but wasteful) -->
<!-- Vue 3: checks user.isActive BEFORE the loop, but user doesn't exist yet → error -->
<li v-for="user in users" v-if="user.isActive" :key="user.id">
{{ user.name }}
</li>The right way to filter a list
Use a computed property:
vue
Open in Vue Playground<script setup lang="ts">
const activeUsers = computed(() => props.users.filter((user) => user.isActive))
</script>
<template>
<li v-for="user in activeUsers" :key="user.id">
{{ user.name }}
</li>
</template>This is better in every way: cached, testable, reusable, and no ambiguity.
The right way to hide an entire list
Wrap with <template v-if>:
vue
Open in Vue Playground<template v-if="shouldShowList">
<li v-for="user in users" :key="user.id">
{{ user.name }}
</li>
</template>Per-item condition inside the loop
Use <template v-for> with v-if on a child element:
vue
Open in Vue Playground<template v-for="user in users" :key="user.id">
<li v-if="user.isActive">
{{ user.name }}
</li>
</template>The ESLint rule vue/no-use-v-if-with-v-for catches this automatically.
See also: What are event modifiers? · Why doesn't v-show work on template elements? · What is v-once and v-memo?
References
- v-if with v-for - Vue.js docs
- Style Guide - Priority A - Vue.js docs
- List Rendering - Vue.js docs