Template renders twice in vue.js

Viewed 694

In my components template I have following code:

   <div
        class="program-point-container"
        v-for="p in programPoints"
        :key="p.id"
    >

        <div class="mt-6 mt-sm-3 mt-md-14 mb-3 font-weight-bold font-size-16" v-if="showDate(p.startsAt)">
            {{new Date(p.startsAt) | moment('DD.MM.YYYY')}} // Humboldt Carré
        </div>
</div>

My programPoints Array has 23 object´s in it. For debug reasons I console logged a string to see that the function is called 23 * 2 times.

In this method I push data into an array. And return true or false to show the div container. But since it already pushed and returned at the "first" rendering. I don't get to show that container.

showDate(datetime) {
        const date = new Date(datetime).toDateString()
        // check if date already been pushed
        if(this.dates.includes(date)) {
            console.log('false')
            return false // date already been pushed
        }
        this.dates.push(date)
        console.log('true')
        return true

    },
1 Answers

Throw away "JQuery thinking". This is NOT how Vue works. Your template is and will be rendered multiple times - simply every time some of the reactive data it uses changes...

You don't need to care about what was rendered before and what to do to update the result for a new data state. It is Vue's job. Just write your template in a "what I want to see on the screen" style and Vue will take care of the rest...

Just remove everything related to showDate - it's not needed. And read the documentation!

Update

In the Array of Objects I have a property which has datetimes. And I only want to display the date once on the first object the date appears. And ignore it for the rest. When a new date appears I want it to show it too but only for the first object and so on

I want to add an extra key to the Object which is the first to iterate and has that unique date. The others will be ignored, till another one comes with an unique date.

First rule of Fight Club...ehm Vue is: Never modify any data which are used in template during template rendering. In best case you end up with incorrect result (as you did). In worst case you end up with infinite loop...

Much better solution is to create alternative data structure that represents what you want and thanks to Vue computed properties it stays up to date even if original data changes...

const vm = new Vue({
  el: "#app",
  data() {
    return {
      programPoints: [
        { id: 1, name: 'Program A', startsAt: '2021/04/13' },
        { id: 2, name: 'Program B', startsAt: '2021/04/14' },
        { id: 3, name: 'Program C', startsAt: '2021/04/14' },
        { id: 4, name: 'Program D', startsAt: '2021/04/18' },
        { id: 5, name: 'Program E', startsAt: '2021/04/13' },
        { id: 6, name: 'Program F', startsAt: '2021/04/18' },        
      ]
    }
  },
  computed: {
    programByDays() {
      return this.programPoints.reduce((acc, item) => {
        if(!acc[item.startsAt]) {
          acc[item.startsAt] = []
        }
        acc[item.startsAt].push(item)
        
        return acc
      }, {})
    }
  },
  mounted() {
    setTimeout(() => this.programPoints.push({ id: 7, name: 'Program SPECIAL', startsAt: '2021/04/20' }), 2000)
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <ul>
    <li v-for="(programs, date) in programByDays" :key="date">
      <p> {{ date }} </p>
      <ul>
        <li v-for="program in programs" :key="program.id">
          {{ program.name }}
        </li>
      </ul
    </li>
  </ul>
</div>

Related