1

I'm currently having an issue with VueJS vuex and Axios :

I'm getting an array with axios -> looping on that array to populate its childs this way : "Rubriques" has a lot of self relations so one rubrique can have a multiple child rubrique

ACTIONS :

  actions: {
    get_main_rubriques: ({ commit }) => {
      axios.get('http://localhost:8180/unv-api/rubrique/search/rsql?query=niveau==1')
        .then(resp => {
          let results = resp.data._embedded.rubriques
          results.forEach(element => {
            axios.get('http://localhost:8180/unv-api/rubrique/search/rsql?query=idpere==' + element.id)
              .then((result) => {
                element.childs = result.data._embedded.rubriques
              })
              .catch((err) => {
                console.log(err)
              })
          })
          console.log(results)
          commit('MUTE_MAIN_RUBRIQUES', results)
        })
        .catch(err => {
          console.log(err)
        })
    }
  }

Mutations :

MUTE_MAIN_RUBRIQUES: (state, rubrique) => {
      state.rubriques = rubrique
    }

APP.VUE

computed: {
    ...mapState([
      'rubriques'
    ])
  },
  created: function () {
    this.$store.dispatch('get_main_rubriques')
  }

<b-dropdown  v-for="item in rubriques" :key="item.id"  v-bind:text="item.libelle" id="ddown1" class="m-md-1">
       <b-dropdown-item-button v-for="child in item.childs" :key="child.id"  v-bind:text="child.libelle">
         {{ child.id }} - {{ child.libelle }}
       </b-dropdown-item-button>
</b-dropdown>

Issue is : the parent dropdown are displaying without any problem but childs are not, they are not present in the state either BUT they are present in the console.log(results) before the commit in my action.

Am I doing something wrong ? Thanks.

2
  • Have you tried Vue.set(state, 'rubriques', rubrique) ?
    – Darem
    Commented Nov 20, 2018 at 13:24
  • @Darem I did yes, doesn't work either, the State 'rubriques' still don't have childs
    – Cesar
    Commented Nov 20, 2018 at 13:29

2 Answers 2

2

move commit('MUTE_MAIN_RUBRIQUES', results) to sit inside the inner then block, to make sure it will get executed after you get back the response:

  actions: {
    get_main_rubriques: ({ commit }) => {
      axios.get('http://localhost:8180/unv-api/rubrique/search/rsql?query=niveau==1')
        .then(resp => {
          let results = resp.data._embedded.rubriques
          results.forEach(element => {
            axios.get('http://localhost:8180/unv-api/rubrique/search/rsql?query=idpere==' + element.id)
              .then((result) => {
                element.childs = result.data._embedded.rubriques;
                console.log(results);
                commit('MUTE_MAIN_RUBRIQUES', results);
              })
              .catch((err) => {
                console.log(err)
              })
          })
        })
        .catch(err => {
          console.log(err)
        })
    }
  }

to understand why its not working on your way, read more about promises and asynchronous operation.

1
  • Alright thanks it's working great, I created an async function instead and it worked too but I didn't know that axios was already waiting for the response before entering in the .then. I've learned something today. Thanks !
    – Cesar
    Commented Nov 21, 2018 at 9:58
1

The basic problem is that computed properties do not do deep watching, see computed property of array is difficult to trigger

This code

computed: {
  ...mapState([
    'rubriques'
  ])
}

references the parent rubriques array and watches that, but not the child properties childs.

When you look at the console, you might see a small 'i' for info that says the value is updated 'just now'. This means by the time you look at it, it already has the child values - but that is probably not the case when the commit() runs.

So, the parents get added and your child fetches keep running but have a reference to their parents, so eventually the children get added correctly to the store.

One way to handle it is to add a deep watcher which uses $forceUpdate() to re-render the DOM

watch: {
  rubriques: {
    handler: function (after, before) {
      this.$forceUpdate()
    },
    deep: true,
  }
}

@Efrat's answer is probably better - you would effectively wait for all child data before posting parents to the store. That way you have just one DOM refresh (and less code).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.