J T
J T

Reputation: 23

Component Updating Out of Order Vue

Here is the following code I have using Bootstrap, Vue, and Axios:

SETUP:

*Ignore the tab-contents in component_a

main.js

Vue.component('component_a', {
  props: ['info'],
  template:   `<div>
    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
      <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
        <ul class="nav nav-tabs" id="myTab" role="tablist" v-for="ep in info">
          <li class="nav-item">
            <a class="nav-link active" id="home-tab" data-toggle="tab" href="#home" role="tab" aria-controls="home"
              aria-selected="true">{{ep.epname}}</a>
          </li>
        </ul>
        <div class="tab-content" id="myTabContent">
          <div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">Raw denim you
            probably haven't heard of them jean shorts Austin. Nesciunt tofu stumptown aliqua, retro synth master
            cleanse. Mustache cliche tempor, williamsburg carles vegan helvetica. Reprehenderit butcher retro
            keffiyeh dreamcatcher synth. Cosby sweater eu banh mi, qui irure terry richardson ex squid. Aliquip
            placeat salvia cillum iphone. Seitan aliquip quis cardigan american apparel, butcher voluptate nisi
            qui.</div>
          <div class="tab-pane fade" id="profile" role="tabpanel" aria-labelledby="profile-tab">Food truck fixie
            locavore, accusamus mcsweeney's marfa nulla single-origin coffee squid. Exercitation +1 labore velit,
            blog sartorial PBR leggings next level wes anderson artisan four loko farm-to-table craft beer twee.
            Qui photo booth letterpress, commodo enim craft beer mlkshk aliquip jean shorts ullamco ad vinyl cillum
            PBR. Homo nostrud organic, assumenda labore aesthetic magna delectus mollit. Keytar helvetica VHS
            salvia yr, vero magna velit sapiente labore stumptown. Vegan fanny pack odio cillum wes anderson 8-bit,
            sustainable jean shorts beard ut DIY ethical culpa terry richardson biodiesel. Art party scenester
            stumptown, tumblr butcher vero sint qui sapiente accusamus tattooed echo park.</div>
            </div>
      </main>
      </div>
  </div>`
}

Vue.component('component_b', {
  data: () => {
    return {
      columns: null
    }
  },
  props: ['info'],  
  template: `<div>
    <div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pt-3 pb-2 mb-3 border-bottom">
    <main role="main" class="col-md-9 ml-sm-auto col-lg-10 px-md-4">
    <h2>EP</h2>
    <table class="table table-striped table-hover">
      <thead>
        <tr>
            <th v-for="col in columns">{{col}}</th>
          </tr>
      </thead>
      <tbody > 
        <tr v-for="row in info">
            <td v-for="col in columns">
              <template v-if="col === 'id'")>
                <a @click="setActiveDisplay(row[col],'activeEp')" href="#">{{row[col]}}</a>
              </template>
              <template v-else>
                {{row[col]}}
              </template>
            </td>
          </tr>
      </tbody>
    </table>
  </main>
  </div>
</div>`,
methods: {
  setActiveDisplay: function (id, ad){
    this.$emit("set-active-display", id, ad)
  }
},
watch: {
  info: {
    immediate: false,
    handler (val,oldVal){
      if (val.length == 0) {
        this.columns = null
      }
        this.columns = Object.keys(val[0])
      }
  }

var app = new Vue({
  el: '#app',
  data () {
    return {
      info: [],
      activeDisplay: "dashboard",
    }
  },
  methods: {
    getAPIData(id,type) {
      axios
        .get(`${SERVER}:${PORT}/api/${type}/${id}`)
        .then(response => {
          this.info = response.data;
        })
        .catch(error => console.log(error.response));
    },
    setActiveDisplay: function (id, ad){
      if(ad === 'ep'){
        if(this.activeDisplay === 'ep'){
          return
        }
        this.getAPIData('*','ep')
      }
      else if(ad === 'activeEp'){
        if(this.activeDisplay === 'activeEp'){
          return
        }
        this.getAPIData(id,'ep')
      }
      this.activeDisplay = ad;
    }
  },
  mounted () {
    this.getData
  }
})

main.html

  <div v-if="activeDisplay === 'ep'">
    <component_b @set-active-display="setActiveDisplay" :info="info"></component_b>
  </div>
  <div v-else-if="activeDisplay === 'dashboard'">
    <dashboard></dashboard>
  </div>
  <div v-else-if="activeDisplay === 'activeEp'">
    <component_a :info="info"></component_a>
  </div>
  <div v-else>
    <dashboard></dashboard>
  </div>

PROBLEM:

After clicking a link in B in the table, it correctly calls the setActiveDisplay method and getAPIData. However, after the getAPIData (i.e., Axios GET call) runs, info (root instance) is not updated to the single item that was clicked in the table in B, and I know this from logging to console. info (root instance) contains all objects that are in the table. (Component B is the first component to display when you open the webpage)

Then, the webpage changes (correctly) to display Component A (due to activeDisplay setting the variable, and displaying the correct component), but I can see the same number of tabs as the number of items in the table. And I see this for a split second. Then, Component A is updated to show the correct one tab that was clicked initially.

I'm sure this is an order of operations issue (as I am new to Vue), but can't seem to figure out why the info (root instance) object is not set to the single item when it is run. I've ran through the debugger in the browser, and the getAPIData is never called a second time. The getAPIData is grabbing the correct data, but it's like Vue is holding the result of the getAPIData call from modifying info until Component A is rendered (even though I am asking it to update after clicking the link in Component B)

Order of Operations

Render B in table with multiple objects in root info => User clicks link in table => setActiveDisplay called in B => event emitted calls setActiveDisplay in root => getAPIData called within root setActiveDisplay (correctly working up until here)=> should update root info object to single item => A rendered with one tab due to root info object (and due to passing to A's prop) containing one object from getAPIData call

EDIT1:

After more debugging, I am seeing that axios.js's dispatchRequest is being called with the correct API url after the click is beginning to be processed by vue.js. . . .So, this this.info = response.data within getAPIData has no data in it after that method finishes. Here is the flow breakdown I've discovered from debugging:

run getAPIData to completion (causing root instances's info object to be undefined => vue.js processes the click event of the item in the table in Component B => axios.js is calling the dispactRequest event with the full URL => more vue.js processing => receiving a response from the getAPIData function and sets the info (root) object to the response

So I see that I will need to wait for the Axios call to complete before displaying the data I need. Question is now, how do I do this within Vue? Thank you.

Upvotes: 2

Views: 271

Answers (1)

tony19
tony19

Reputation: 138266

So I see that I will need to wait for the Axios call to complete before displaying the data I need. Question is now, how do I do this within Vue?

I assume you need to wait for the axios.get() call in getApiData() to complete before setting this.activeDisplay. To do this, first return the result of axios.get() (i.e., a Promise), so that callers could await it.

getAPIData(id,type) {
  // BEFORE:
  //axios.get(...)

  // AFTER:
  return axios.get(...)
}

Then make setActiveDisplay() an async function, allowing the result of getApiData() to be awaited:

// BEFORE:
//setActiveDisplay: function (id, ad) { 

// AFTER:
async setActiveDisplay(id, ad) {
  if (...) {
    await this.getApiData(...)
  }
  this.activeDisplay = ad
}

Alternatively, you could do without async/await and use Promise.prototype.then():

setActiveDisplay: function (id, ad) {
  if (...) {
    this.getApiData(...).then(() => this.activeDisplay = ad)
  }
}

Upvotes: 1

Related Questions