Alexander Schillemans
Alexander Schillemans

Reputation: 566

Emit does not work inside axios call, but does outside

I want to emit data to a parent component after a succesful axios call, but for some reason emit does not work when resolving the promise. Emit works fine when putting it before or after the axios call.

I can't seem to figure out what's wrong. I'm using arrow functions so this shouldn't be changed, and even then - I tried doing it with functions and assigning this to a variable before going into the axios call but that didn't work either.

I made a custom config for axios, but changing it back to the default does not change anything either.

// ApiWrapper.js
// this is the custom axios config I'm using

import axios from 'axios';

const apiWrapper = axios.create({
    baseURL: 'my_url',
})

apiWrapper.interceptors.request.use(function(config) {
    let hasToken = localStorage['token'] || false;

    if(hasToken) {
        config.headers['Authorization'] = `Bearer ${localStorage.getItem('token')}`
    }

    return config
}, function(error) {
    return Promise.reject(error);
})

export default apiWrapper;

My App, which is the parent and listens to the token event.

<script>
// App.vue

import { generalState } from './helpers/GeneralState';
import Loading from './components/generic/Loading.vue';
import LoginPage from './views/LoginPage.vue';

export default {
    name: 'App',
    components: {
      Loading,
      LoginPage
    },
    data() {
      return {
        token: null,
        generalState: generalState
      }
    },
    methods: {
      setToken(token) {
        console.log('in setToken');
        console.log(token);
        this.token = token;
        localStorage.setItem('token', token);
      }
    }
  }
</script>

<template>
  
  <div class="flex h-screen bg-sgray">
    <div class="m-auto">

      <section class="h-full gradient-form md:h-screen">
        <div class="container py-12 px-6 h-full">
            <div class="flex justify-center items-center flex-wrap h-full g-6 text-gray-800">
                <div class="block bg-white shadow-lg rounded-lg">
                  <template v-if="generalState.isLoading">
                    <Loading/>
                  </template>
                  <template v-else-if="!token">
                    <LoginPage @token="setToken" />
                  </template>
                  <template v-else>
                    <router-view/>
                  </template>
                </div>
            </div>
        </div>
      </section>
      
    </div>
  </div>
</template>

And finally, my LoginPage which should emit the returned token to the parent component.

// LoginPage.vue

<script>
import Input from '../components/generic/Input.vue';
import Button from '../components/generic/Button.vue';
import apiWrapper from '../helpers/ApiWrapper';
import { generalState } from '../helpers/GeneralState';

export default {
    components: {
        Input,
        Button
    },
    data() {
        return {
            form: {
                username: '',
                password: ''
            },
        }
    },
    methods: {
        process_form(e) {
            generalState.isLoading = true;
            let token = null;

            this.$emit('token', 'TOKEN-BEFORE');

            apiWrapper.post('login/', {
                email: this.form.username,
                password: this.form.password
            }).then(resp => {
                token = resp.data.token;
                this.$emit('token', token);
            }).finally(() => {
                generalState.isLoading = false;
            });

            this.$emit('token', 'TOKEN-AFTER');
        }
    },
}
</script>

<template>
    <div class="lg:flex lg:flex-wrap g-0">
        <div class="lg:w-6/12 px-4 md:px-0">
            <div class="md:p-12 md:mx-6">
                <h4 class="text-xl font-semibold mt-1 mb-12 pb-1">Sign in</h4>
                <form v-on:submit.prevent="process_form">
                    <p class="mb-4">Manage all your integrations.</p>
                    <div class="mb-4">
                        <Input v-model="form.username" inputType="text" id="username" name="username" placeholder="Username"/>
                    </div>
                    <div class="mb-4">
                        <Input v-model="form.password" inputType="password" id="password" name="password" placeholder="Password" />
                    </div>
                    <div class="text-center pt-1 mb-12 pb-1">
                            <Button buttonType="secondary" role="submit">
                                Log in
                            </Button>
                        <a class="text-gray-500 hover:underline" href="#!">Forgot password?</a>
                    </div>
                    <div class="text-center pb-6">
                        <p class="mb-3 mr-2">Don't have an account?</p>
                        <Button buttonType="primary" role="button">
                            Sign up
                        </Button>
                    </div>
                </form>
            </div>
        </div>
        <div class="lg:w-6/12 flex items-center lg:rounded-r-lg rounded-b-lg lg:rounded-bl-none bg-sred">
            <div class="text-white px-4 py-6 md:p-12 md:mx-6">
                <h4 class="text-xl font-semibold mb-6">One platform for all integrations.</h4>
                <p class="text-sm">
                Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
                tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
                quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
                consequat.
                </p>
            </div>
        </div>
    </div>
</template>

This is the part that is not working:

methods: {
        process_form(e) {
            generalState.isLoading = true;
            let token = null;

            this.$emit('token', 'TOKEN-BEFORE'); // <-- This emit DOES work

            apiWrapper.post('login/', {
                email: this.form.username,
                password: this.form.password
            }).then(resp => {
                token = resp.data.token;
                this.$emit('token', token); // <-- This emit DOES NOT work
            }).finally(() => {
                generalState.isLoading = false;
            });

            this.$emit('token', 'TOKEN-AFTER'); // <-- This emit DOES work
        }
    },

I'm pulling my hair out because I cannot see why the emit inside the axios is not working. Can anybody help me out? Thanks in advance.

Upvotes: 0

Views: 107

Answers (1)

Lk77
Lk77

Reputation: 2462

Your LoginPage component is displayed using a v-else-if :

<!--  when generalState.isLoading is true -->
<template v-if="generalState.isLoading">
    <Loading/>
</template>
<!--  that component get removed, 
which means the token event listener get also removed -->
<template v-else-if="!token">
    <LoginPage @token="setToken"/>
</template>

What you can do is :

1 / don't use v-else-if, but a simple v-if instead, so your component is not removed

2 / Use a v-show to hide your component without removing it, because you might encounter styling issues, it's cleaner to hide it so your loader is displaying fine.

<!--  when generalState.isLoading is true -->
<template v-if="generalState.isLoading">
    <Loading/>
</template>
<!--  that component do not get removed but just hidden -->
<template v-if="!token" v-show="!generalState.isLoading">
    <LoginPage @token="setToken"/>
</template>

What is happening is that your axios callback get actually called, but your component has been removed by then and is going to be destroyed, so the parent component no longer listen to the events that the component is emitting

Upvotes: 1

Related Questions