In Vue 3, what happens when v-if and v-for are placed on the same element, and how did that change from Vue 2?
answer
- which directive runs first
- the loop alias is not in scope
- reversed from Vue 2
- wrap with <template>
basics
~20 sIn Vue 3, v-if takes priority over v-for on the same element, so the condition cannot see the loop alias; Vue 2 ran v-for first. Move the loop to a wrapping <template> or the condition to a container.
solid answer
~40 sWhen both sit on one element, Vue 3 evaluates **`v-if` first**, outside the loop. A condition that reads the alias, as in `v-for="todo in todos" v-if="!todo.done"`, looks up `todo` on the component, finds nothing, and fails at render: Vue warns that the property was accessed during render but is not defined, and reading `.done` on `undefined` throws. **Vue 2 did the opposite**, running `v-for` first, so old per-item filtering code breaks after upgrading. The guide recommends never combining them on one element. For per-item conditions, put `v-for` on a `<template>` and `v-if` on the inner element (or filter in a computed); to hide the whole list, move `v-if` to the containing `<ul>`.
code
vue · 17 lines<script setup lang="ts">
import { ref } from 'vue'
const showTodos = ref(true)
const todos = ref([
{ id: 1, name: 'Write tests', isComplete: false },
{ id: 2, name: 'Ship', isComplete: true }
])
</script>
<template>
<ul v-if="showTodos">
<template v-for="todo in todos" :key="todo.id">
<li v-if="!todo.isComplete">{{ todo.name }}</li>
</template>
</ul>
</template>go deeper
Remember that Vue 3 checks v-if before v-for on the same element and that the guide says not to combine them there.
Explain why the alias is undefined inside the condition, and show the <template v-for> and container-level fixes.
Catch this in a Vue 2 to 3 upgrade, where per-item filters silently became render errors, and restructure templates rather than patching conditions.
Adopt a lint-enforced rule against same-element v-if and v-for so template meaning never depends on a precedence that changed between majors.
## The rule When `v-if` and `v-for` appear on the **same element**, Vue 3 gives `v-if` the **higher priority**. The condition is evaluated once, outside the loop, and only if it is truthy does the loop run. The consequence: the `v-if` expression **cannot see the loop's aliases**, because they do not exist yet. ```vue-html <!-- broken in Vue 3 --> <li v-for="todo in todos" v-if="!todo.isComplete">{{ todo.name }}</li> ``` Here `todo` in the `v-if` is resolved against the component instance, where no such property exists. In development Vue warns that the property was accessed during render but is not defined on the instance, and then `todo.isComplete` throws because `todo` is `undefined`. ## What changed from Vue 2 | | Vue 2 | Vue 3 | |---|---|---| | Same-element precedence | `v-for` first | `v-if` first | | `v-if` reading the alias | worked, filtered per item | alias undefined, render error | | `v-if` on a whole-list flag | re-evaluated for every item | evaluated once | | Official advice | avoid combining | avoid combining | The Vue 3 migration guide lists this as a breaking change. Code that relied on Vue 2's order to filter items silently changed meaning; code that used a whole-list flag kept working and became cheaper. ## The two legitimate intents and their fixes Developers combine the two directives for one of two reasons, and each has a clean replacement: 1. **Show only some items** (per-item condition). Move `v-for` onto a wrapping `<template>` and keep `v-if` on the element inside it, which makes the order explicit. Alternatively, iterate a computed list that already contains only the wanted items. 2. **Hide the whole list** (list-level condition). Move `v-if` to the container element, such as the `<ul>`, so the check reads as what it is. ```vue-html <!-- per-item: loop outside, condition inside --> <template v-for="todo in todos" :key="todo.id"> <li v-if="!todo.isComplete">{{ todo.name }}</li> </template> <!-- list-level: condition on the container --> <ul v-if="showTodos"> <li v-for="todo in todos" :key="todo.id">{{ todo.name }}</li> </ul> ``` Note that in the `<template v-for>` form the `:key` goes on the `<template>` tag in Vue 3. ## Why the guide discourages it even when it works A same-element `v-if` that uses only a component-level flag (`v-if="showTodos"`) runs correctly in Vue 3, but it reads as a per-item filter, and a reader cannot tell which directive wins without knowing the precedence rule. Since that rule flipped between major versions, an explicit structure is safer for everyone who maintains the template. ## Checklist for an upgrade or review - Search templates for elements carrying both `v-for` and `v-if`. - If the `v-if` reads the loop alias, it is a bug in Vue 3: restructure it. - If it reads only component state, move it to the container for clarity. - In the Vue 3 migration build, a compat flag named `COMPILER_V_IF_V_FOR_PRECEDENCE` covers this change while code is being ported. ## Interview traps - **"v-if filters the list."** Only when it sits inside the loop; on the same element in Vue 3 it gates the whole loop. - **"The compiler catches it."** It does not: the template compiles, and the failure appears when the component renders. - **"`v-show` avoids the problem."** Replacing `v-if` with `v-show` on the same element makes the alias readable, but every item is still rendered and merely hidden, which is not filtering. - **"The fix is a method call in `v-if`."** A method does not change precedence; the alias is still out of scope.
- Does `<li v-for="t in todos" v-if="showTodos">` work in Vue 3?Yes, because `showTodos` is component state, not the loop alias; the condition is checked once and gates the whole loop. It still goes against the guide's advice, so move the `v-if` to the `<ul>` where its meaning is obvious.
- What should you look for when upgrading a Vue 2 app because of this precedence change?Every element carrying both `v-for` and a `v-if` that reads the loop alias. In Vue 2 those filtered per item; in Vue 3 the alias is undefined in the condition and rendering fails. Restructure them to `<template v-for>` with an inner `v-if`, or iterate a filtered list.
saying these in an interview costs you the question
- In Vue 3, v-for runs before v-if on the same element.
- A same-element v-if can filter items by reading the loop alias.
- The precedence is the same in Vue 2 and Vue 3.
- Putting both on one element is the recommended filtering pattern.