Vue.js component does not emit to parent after successful axios call

Viewed 1711

I am using

"axios": "^0.19.0",
"vue": "^2.6.10",
"vuex": "^3.1.1"

My vuex action looks like this and calls a remote interface using axios. The request does work and it receives a valid response.

skipQuestion({commit}, payload) {
    let params = {
        answer: {
            id: payload.id,
            skipped: true,
        }
    };

    return new Promise((resolve, reject) => {
        commit(UPDATE_LOADING, true);
        Remote.put(`/answer.json`, params)
            .then((response) => {
                commit(UPDATE_LOADING, false);
                commit(SKIP_QUESTION, payload.id);
                resolve();
            })
            .catch((error) => {
                commit(UPDATE_LOADING, false);
                reject(error);
            })
    })
},

The component Question does have the following method skip, which calls the vuex action skipQuestion and should emit the skip event to the parent component.

...mapActions(['skipQuestion']),
skip(evt) {
    let payload = { id: this.question_id };
    this.skipQuestion(payload).then( () => {
        this.$emit('skip', this.uuid);
    }).catch( (error) => {
        console.log(error);
    });
},

The problem is, that the skip event is not emitted to the parent when using it within the then block of the action. The vue developer console for chrome also confirms, that the skip event was fired. If I put the emit outside the block, everything works. Any suggestions whats going wrong?

Edit 1

Also tried the following code and both log statements are printed to the console.

skip(evt) {
    let payload = { id: this.question_id };
    let vm = this; 
    this.skipQuestion(payload).then( () => {
        console.log('before skip emit');
        vm.$emit('skip', this.uuid);
        console.log('after skip emit');
    }).catch( (error) => {
        console.log(error);
    });
},

Edit 2

Here is the template code which is used to listen to the child events:

<question v-bind="question"
          :key="question.uuid"
          v-if="questionReady"
          v-on:skip="onSkipQuestion"
          v-on:answer="onAnswerQuestion">
</question>

As told before, if not using the Promise/then-block returned by the axios request, the emit does work. Below you'll find the method which should be called:

methods: {
  onSkipQuestion(payload) {
    // this code is not executed
    console.log('onSkipQuestion')

    //....
  },


  //....
}      
4 Answers

Summarize your information given:

The vue developer console for chrome also confirms, that the skip event was fired.


TL;DR


Basically you can debug using debugger statement:

skip(evt) {
    let payload = { id: this.question_id };
    this.skipQuestion(payload).then( () => {
        debugger; // scope -> _events & scope -> $parent.componentInstance
        // or console.log(JSON.stringify(this._events))
        this.$emit('skip', this.uuid);
    }).catch( (error) => {
        console.log(error);
    });
},

Then you know whats happening.


Things to check:

  • valid this scope

  • valid parent

  • actually triggered as resolved

  • vm._events registered

You have lost the reference to this inside the then block. The reference is now the callback function called. Instead do this

 ...mapActions(['skipQuestion']),
    skip(evt) {
        let payload = { id: this.question_id };
        let vm = this; // Preserve Vue instance for use inside block
        this.skipQuestion(payload).then( () => {
            vm.$emit('skip', vm.uuid);
        }).catch( (error) => {
            console.log(error);
        });
    },

Maybe the problem is that you don't return resolve() or reject() in skipQuestion().

Anyway, assuming your application uses ES2017 (or higher), or if not then Babel, I would refine the function to use async / await structure so that this would not be at any risk (referring to Varun's answer), and not in any risk if skipQuestion() doesn't return anything, like as is currently:

async skip(evt) {
    let payload = { id: this.question_id };
    try {
      let result = await skipQuestion(payload)
      this.$emit('skip', this.uuid);
    } catch(error){
      console.log(error);
    }
}

Not returning resolve / reject is a known source of bugs in case of Promise((resolve, reject) => {...}) syntax

I had this exact same problem and could not for the life of me figure out what was going on. All I can think of is that somehow the component is losing the ability to emit events while awaiting the promise resolution.

Anyway, my solution was to emit the promise itself, like this:

skip(evt) {
    let payload = { id: this.question_id };
    this.$emit('skip', skipQuestion(payload));
}

And on the parent, you can do

... @skip="receive_skip($event)" ...

...

methods: {
    receive_skip(skipped) {
        skipped
        .then((data) => {
            // do something on success
        })
        .catch((err) => {
            // do something on fail
        });
    }
}

It's not as clean and elegant, but it gets the job done.

Related