First, let's start with a general guideline, given that it's been suggested in other answers: Never use v-if
and v-for
on the same element, ever.
Since both directives control whether an element gets rendered, using them together is confusing and error prone. In Vue 2, v-for
takes precedence over v-if
, but in Vue 3, it's vice-versa– always avoid using them together.
For your problem, where you have a list of lists, you're really looking to handle two kinds of null values: null lists, and null items in those lists.
We can handle both by weaving together multiple v-if
s and v-for
s like this, a pair of each for both lists, with a third v-if
to avoid rendering empty child lists:
<ul>
<template v-for="item in Object.values(items)">
<li v-if="item != null" :key="item.id">
{{ `Parent #${item.id}: ${item.title}` }}
<ol v-if="item.children != null && Object.values(item.children).length > 0">
<template v-for="child in Object.values(item.children)">
<li v-if="child != null" :key="child.id">
{{ `Child #${child.id}: ${child.subtitle}` }}
</li>
</template>
</ol>
</li>
</template>
</ul>
This might seem a bit complicated at first, but here's the core: we can solve this while avoiding the v-if
with v-for
restriction with <template>
elements as wrappers.
By using <template>
s as wrappers around our list items, in both the parent and child lists, we can both:
- Render the items in the parent and child lists with
v-for
- Choose whether to render each item individually with
v-if
, without creating a bunch of empty <li>
s along the way
Runnable snippet example:
new Vue({
el: '#app',
data() {
return {
items: {
1: {
id: 1,
title: "This should be rendered",
children: {
100: {
id: 100,
subtitle: "I am a child"
},
101: null,
102: {
id: 102,
subtitle: "I am a second child"
},
}
},
2: null, // No parent #2 to render
3: {
id: 3,
title: "Should be rendered as well",
children: {
300: {
id: 300,
subtitle: "I am a child under a different parent"
},
}
},
4: {
id: 4,
title: "Should also be rendered (without children)",
children: null
},
}
}
},
});
<script src="https://unpkg.com/vue@2/dist/vue.min.js"></script>
<div id="app">
<ul>
<template v-for="item in Object.values(items)">
<li v-if="item != null" :key="item.id">
{{ `Parent ${item.id}: ${item.title}` }}
<ol v-if="item.children != null && Object.values(item.children).length > 0">
<template v-for="child in Object.values(item.children)">
<li v-if="child != null" :key="child.id">
{{ `Child ${child.id}: ${child.subtitle}` }}
</li>
</template>
</ol>
</li>
</template>
</ul>
</div>
Using layered v-if
s and v-for
s with <template>
s to render lists is a great way to give you maximum control over what gets rendered, while also helping avoid the pitfalls of using both directives on the same element.