Skip null items and null children in Vue v-for
Asked Answered
M

6

34

I have a nested for ... in loop in Vue.js. What I'm trying to do is to skip elements if the value of the element is null. Here is the Vue template code:

<ul>
    <li v-for="item in items" track-by="id">
        <ol>
            <li v-for="child in item.children" track-by="id"></li>
        </ol>
    </li>
</ul>

null elements may be present in both item and item.children objects.

For example:

var data = {
   1: {
      id: 1,
      title: "This should be rendered",
      children: {
          100: {
              id: 100,
              subtitle: "I am a child"
          },
          101: null
      }
   },
   2: null,
   3: {
       id: 3,
       title: "Should should be rendered as well",
       children: {}
   }
};

With this data data[1].children[101] should not be rendered and if data[1].children[100] becomes null later it should be omitted from the list.

I'm not in control of how this data is structured, so I have to deal with it in this form.

Messina answered 24/3, 2016 at 16:48 Comment(0)
C
33

A simple v-if might work:

<li v-for="item in items" v-if="item !== null" track-by="id">

Give it a try. If not, do this:

You can add a filter for that (in main.js before your App instance):

Vue.filter('removeNullProps', function(object) {
  return _.reject(object, (value) => value === null)
})

then in the template:

<li v-for="item in items | removeNullProps" track-by="id">
    <ol>
        <li v-for="child in item.children | removeNullProps" track-by="id"></li>
    </ol>
</li>
Chapel answered 24/3, 2016 at 18:44 Comment(3)
Need vue-underscore?Confederate
FYI, Vue js style guide advises to avoid using v-for and v-if on the same element. vuejs.org/v2/style-guideIntoxicated
I had some problems with this answer. But since the v-if="<expression>" I ended up with just this v-if="item". if item is null, then the expressions will be null and not true, and therefore remove from the DOM. if your intent is to just hide, use v-show instead.Scyros
P
17

In Vue 2, filters have been deprecated in v-fors.

Now you should use computed properties. Demo below.

new Vue({
  el: '#app',
  data: {
    items: [
      'item 1',
      'item 2',
      null,
      'item 4',
      null,
      'item 6'
    ]
  },
  computed: {
    nonNullItems: function() {
      return this.items.filter(function(item) {
        return item !== null;
      });
    }
  }
})
<script src="https://unpkg.com/vue@2"></script>
<div id="app">
  Using a computed property:
  <ul>
    <li v-for="item in nonNullItems">
      {{ item }}
    </li>
  </ul>
  <hr>
  Using v-if:
  <ul>
    <li v-for="item in items" v-if="item !== null">
      {{ item }}
    </li>
  </ul>
</div>
Polygraph answered 1/4, 2018 at 21:17 Comment(2)
That's good, how can I apply this function to print '0' insted of hiding the null values?Horsewhip
@Horsewhip you mean this jsfiddle.net/acdcjunior/6gmqkzoe/1 ?Polygraph
B
4

Just use v-if to do with it. But the first, do not use track-by="id" because of the null item and null child. You can check the demo here https://jsfiddle.net/13mtm5zo/1/.

Maybe the better way is to deal with the data first before the render.

Beebread answered 25/3, 2016 at 2:36 Comment(0)
M
4

I would advise you against using v-if and v-for in the same element. What I found worked and didn't affect performance is this :

<li v-for="(value, key) in row.item.filter(x => x !== null)" :key="key"{{value}}</li>

You just need to run the filter function on the array you are going through. This is a common use in c# and found it was no different in JavaScript. This will basically skip the nulls when iterating.

Hope this helps (3 years later).

Matzo answered 22/2, 2020 at 19:56 Comment(0)
B
0

Vue.Js style guide tells us to:

"Never use v-if on the same element as v-for."

How to handle v-if with v-for properly according to the Vue style guide:

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

See more on how to handle v-if with v-for here : https://v2.vuejs.org/v2/style-guide/#Avoid-v-if-with-v-for-essential

Baughman answered 12/12, 2019 at 10:3 Comment(4)
While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - From ReviewCharwoman
Hey @NicoHaase thanks for the review it's my first post here ! i've edited my post with a real life exemple.Baughman
hi, how if the user === null, it will get component lists rendered with v-for should have explicit keys how handle this?Topcoat
@AgnesPalit instead of the user id you can use the element array index like so <li v-for="(user, index) in users" :key="index">...Baughman
B
0

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-versaalways 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-ifs and v-fors 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:

  1. Render the items in the parent and child lists with v-for
  2. 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-ifs and v-fors 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.

Bluet answered 5/4, 2023 at 17:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.