Why is this an issue?

Using v-if and v-for on the same element is a common Vue mistake that causes two different problems depending on what the v-if condition references.

When v-if references a variable defined by v-for, the intent is to filter the list to matching items only. In Vue 3, v-if has higher priority than v-for, so the loop variable is not in scope when v-if is evaluated, which causes a runtime error. Even in Vue 2, where v-for has higher priority and the variable is accessible, iterating over all items before filtering them is unnecessary work. Replace the loop variable with a computed property that returns the filtered array.

When v-if is unrelated to the loop variable, the condition controls whether the entire list should be rendered. Placing it on the loop element re-evaluates the condition once per item instead of once for the whole list. Move the v-if to a parent element so it is evaluated only once.

Code examples

Noncompliant code example

<template>
  <ul>
    <li v-for="user in users" v-if="user.active" :key="user.id"> <!-- Noncompliant: "v-if" cannot access "user", which is defined by "v-for" -->
      {{ user.name }}
    </li>
  </ul>
</template>

<script>
export default {
  props: { users: Array },
}
</script>

Compliant solution

<template>
  <ul>
    <li v-for="user in activeUsers" :key="user.id">
      {{ user.name }}
    </li>
  </ul>
</template>

<script>
export default {
  props: { users: Array },
  computed: {
    activeUsers() {
      return this.users.filter(user => user.active);
    }
  }
}
</script>

Noncompliant code example

<template>
  <ul>
    <li v-for="user in users" v-if="user.active" :key="user.id"> <!-- Noncompliant: "v-if" cannot access "user", which is defined by "v-for" -->
      {{ user.name }}
    </li>
  </ul>
</template>

<script setup lang="ts">
const props = defineProps<{ users: { name: string; active: boolean }[] }>();
</script>

Compliant solution

<template>
  <ul>
    <li v-for="user in activeUsers" :key="user.id">
      {{ user.name }}
    </li>
  </ul>
</template>

<script setup lang="ts">
import { computed } from 'vue';

const props = defineProps<{ users: { name: string; active: boolean }[] }>();
const activeUsers = computed(() => props.users.filter(user => user.active));
</script>

Noncompliant code example

<template>
  <ul>
    <li v-for="user in users" v-if="showList" :key="user.id"> <!-- Noncompliant: "showList" is re-evaluated for each item in the loop -->
      {{ user.name }}
    </li>
  </ul>
</template>

Compliant solution

<template>
  <ul v-if="showList">
    <li v-for="user in users" :key="user.id">
      {{ user.name }}
    </li>
  </ul>
</template>

Resources

Documentation