How to keep track of an array change in Vue.js when the index is a dynamic value?

I am building an app using Node.js and Vue. My DATA for the component is the following:

data() {
    return {
        campaign: {
          buses: [],
          weeks: [
            {
              oneWayBuses: [],
              returnBuses: []
            }
          ]
        },
        busesMap: {
          // id is the bus ID. Value is the index in the campaign.buses array.
        },
    };
  },

I fill the buses and weeks array in MOUNTED section in two separate methods after getting the data from the server:

   responseForWeeks => {
      responseForWeeks.forEach(
        week => this.campaign.weeks.push(week);
      )
   }

   responseForBuses => {
      responseForBuses.forEach(
         bus => this.campaign.buses.push(bus);
         // Here I also fill the busesMap to link each week to its bus index in the array of buses
         this.busesMap[bus.id] = this.campaign.buses.length - 1;
      )
   }

So the idea is that my busesMap looks like busesId keys and index values:

   busesMap = {
      'k3jbjdlkk': 0,
      'xjkxh834b': 1,
      'hkf37sndd': 2

   }

However, when I try to iterate over weeks, v-if does not update so no bus info is shown:

   <ul>
      <li 
         v-for="(busId, index) in week.oneWayBuses"
         :key="index"
         :item="busId"
      >
         <span v-if="campaign.buses[busesMap.busId]">
            <strong>{{ campaign.buses[busesMap.busId].busLabel }}</strong>
             leaves on the
            <span>{{ campaign.buses[busesMap.busId].oneWayDepartureDate.toDate() | formatDate }}</span>
         </span>
      </li>
   </ul>

On the other side, if I shorten the v-if condition to campaign.buses, then I get into the condition but campaign.buses[busesMap.busId] is still undefined, so I get an ERROR trying to display busLabel and oneWayDepartureDate

I've read vue in depth documentation, but couldn't come up with a resolution. Any gotchas you can find out?

2 answers

  • answered 2019-12-09 13:54 Michael

    Try this:

    async mounted(){
      await responseForWeeks
      await responseForBuses
    responseForWeeks => {
          responseForWeeks.forEach(
            week => this.campaign.weeks.push(week);
          )
       }
    // this is partial since it is what you provided
       responseForBuses => {
          responseForBuses.forEach(
             bus => this.campaign.buses.push(bus);
             // Here I also fill the busesMap to link each week to its bus index in the array of buses
             this.busesMap[bus.id] = this.campaign.buses.length - 1;
          )
       }
       }
    

    Basically you want to make sure that before your component loads your data is in place. You can also create computed properties which will force re rendering if dependencies are changed and they are in the dom.

  • answered 2019-12-09 15:51 Santironhacker

    Actually, the problem was indeed in the HTML. When trying to access the object keys, better use [] intead of a dot .

    Final HTML result would be as follows:

    <ul>
       <li 
          v-for="(busId, index) in week.oneWayBuses"
          :key="index"
          :item="busId"
       >
           <span v-if="campaign.buses[[busesMap[busId]]]">
              <strong>{{ campaign.buses[busesMap[busId]].busLabel }}</strong>
              leaves on the 
              <span>{{ campaign.buses[busesMap[busId]].oneWayDepartureDate.toDate() | formatDate }}</span>
           </span>
        </li>
    </ul>
    

    What was happening is that previously campaign.buses[busesMap.busId] did not exist, thus not rendering anything. Solved to campaign.buses[busesMap[busId]]. Also used claudators for the displayed moustache sintach.

    Hope it helps someone else messing with Objects!