Deleted element is there after deletion until refresh - VueJS

I am trying to create a simple application where I have a list of providers and wanna add/delete them… the same issue is for both cases

when I try to delete or element and when I try to add an element (provider)

Here is the situation:

Provider.vue : * loading providers from and API (simple as that)

<template lang="html">
 <div class="container">
   <h1 class="h2">{{ $t('default.titles.providers') }}</h1>
     <button class="btn btn-success my-3 float-right" @click="addProvider">{{ $t('default.buttons.add') }}</button>
     <template v-if="showError">
       <div class="clearfix"></div>
       <tw-loading-error :message="this.error"></tw-loading-error>
     </template>
     <template v-else>
       <table class="table table-hover table-sm">
         <thead>
           <tr>
             <th scope="col">#</th>
             <th scope="col">{{ $t('default.provider.name') }}</th>
             <th scope="col"></th>
             <th scope="col"></th>
             <th scope="col"></th>
           </tr>
         </thead>
         <tbody>
           <tr v-for="(provider, index) in providerList" :key="index">
             <th scope="row">{{index+1}}</th>
             <td>{{provider.provider_name}}</td>
             <td :title="$t('default.buttons.edit')">
               <router-link :to="{ name: 'edit-provider', params: { provider } }">
                 <Edit2Icon
                   class="pointer">
                 </Edit2Icon>
              </router-link>
             </td>
             <td :title="$t('default.buttons.delete')">
               <router-link class="delete" :to="{ name: 'delete-provider', params: { provider } }">
                   <DeleteIcon
                     class="pointer">
                   </DeleteIcon>
               </router-link>
             </td>
             <td :title="$t('default.buttons.add-client')">
               <router-link class="edit" :to="{ name: 'add-provider-user',
                                                params: {
                                                  provider_id: provider.id_provider,
                                                  provider_name: provider.provider_name
                                               }}">
                 <UserPlusIcon class="pointer"></UserPlusIcon>
               </router-link>
             </td>
           </tr>
         </tbody>
       </table>
     </template>
 </div>
</template>

<script>
  import ProviderService from './../../../services/provider.service'
  import { Edit2Icon, DeleteIcon, UserPlusIcon } from 'vue-feather-icons'
  import LoadingError from './../../shared/alerts/LoadingError.vue'

  export default {
    name: "Provider",
    data() {
      return {
        providerList: [],
        error: '501: cannot load the providers :(',
        showError: false
      }
    },
    components: {
      'tw-loading-error': LoadingError,
      Edit2Icon, DeleteIcon, UserPlusIcon
    },
    methods: {
      getProviders() {
        ProviderService.getProviders().then(
          response => {
            this.providerList = response.data
          },
          error => {
            this.showError = true
            this.error = error
          }
        )
      },
      addProvider() {
        this.$router.push({name: "add-provider"})
      },
    },
    mounted() {
      this.getProviders();
    }
  }
</script>

<style lang="css" scoped>
.pointer {
  cursor: pointer;
}
.delete {
  color: red;
}
</style>

provider.service.js : * very straightforward … I either get all provider or delete a provider

import axios from '../axios/axios-conf'
import authHeader from './auth-header';

class ProviderService {
  async getProviders() {
    const response = await axios.get('/providers', { headers: authHeader() })
    return response
  }

  async deleteProvider(id){
     try {
       return await axios.delete('/provider/delete/'+id)
     } catch (error) {
       return error
     }
  }
}

 export default new ProviderService()

DeleteProvider.vue : * here is the culprit

<template lang="html">
 <div class="container">
   <h1 class="h2">{{$t('default.titles.delete')}} {{provider.name}}?</h1>
      <div class="container">
        <button class="btn btn-danger" @click="deleteProvider(provider.id)">{{$t('default.buttons.yes')}}</button>
        <button class="btn btn-sm btn-secondary" @click="goBack">{{$t('default.buttons.no')}}</button>
      </div>
 </div>
</template>

<script>
//  import axios from './../../../axios/axios-conf'
  import ProviderService from './../../../services/provider.service'

  export default {
    name: "DeleteProvider",
    data() {
      return {
        provider: {
          id: null,
          name: null
        }
      }
    },
    components: {
    },
    methods: {
      async deleteProvider(id){
        let c = confirm("Do you really want to delete it? You will not be able to restore this data again!")
        if(c){
          this.delete(id)
              .then(success => {
                return success
              })
              .catch(error => {
                return error
              })
          this.goBack()
        }
      },
      async delete(id){
        let response = await ProviderService.deleteProvider(id)
        return response
      },
      checkParams(){
          if(JSON.stringify(this.$route.params) === '{}'){
            this.goBack()
          }
      },
      goBack(){
        this.$router.push({ name: 'provider'})
      }
    },
    mounted() {
      this.provider.name = this.$route.params.provider.provider_name
      this.provider.id = this.$route.params.provider.id_provider
    },
    created() {
      this.checkParams()
    }
  }
</script>

<style lang="css" scoped>
.pointer{
  cursor: pointer
}
</style>

the APIs work flawlessly … they do what is expected… the problem occurs, when I try to delete a provider VueJS redirects me (the router) so fast to the list of providers that the element does not disappear (the one deleted). Only after I refresh the page (either via F5 or going to another page and back to render it again) it is projected.

So the fact is, it is successfully deleted but the page does not wait or the deletion to project it right away.

What am I doing wrong? I do have async/await in my code for all functions (desperate situation calls for desperate measures :smiley: )

PS: worth mentioning: it is happening only every now and then … sometimes it works ok (so the element is not shown … sometimes I have to refresh the page)

Thx

Add :key="componentKey" to the element.

then in your script

data() {
  return {
    componentKey: 0
  }
}

Then in your delete method increment the component key.

this.componentKey++

Since you are using a component in another component, you’ll likely have to emit the componentKey change to the parent and handle it there.

and this will assure that the loading will be synchronous? 8-O

do I add the “componentKey: 0” to DeleteProvider.vue? or Provider.vue ?? Sorry I don’t really get the purpose :frowning: could you please elaborate more on how this will help?

Here is a post that elaborates on what I’m talking about: https://michaelnthiessen.com/force-re-render/

The above link talks about different ways to force a component to refresh. Here is the one specific to key changing: https://michaelnthiessen.com/key-changing-technique/

thx I run through that but it seems more like an ugly workaround … doesn’t seem like a best practice to me :frowning: is it something programmers really do?

My question is rather more of: “Why doesn’t the async/await work?” It should be working … why does VueJS doesn’t wait to complete the deletion and get the data again AFTER the deletion? so they are fresh and I don’t have to submit this :key value every time I do the change to the component :frowning:

Afraid not. This bit is not using await, it’s using then:

this.delete(id)
  .then(success => {
    return success
  })
  .catch(error => {
    return error
  })
this.goBack()

It is important to appreciate that async/await is just a wrapper around promises.

Putting async on a function forces the return value to be a promise.

Using await is just a shortcut for writing then. It doesn’t really wait. The code that follows it (inside the same function) will be moved into the implicitly created then handler, giving the impression that synchronous code waited.

From the perspective of the caller (outside the function) there is no waiting. The function just returns a promise immediately.

Your deleteProvider implementation in provider.service.js doesn’t seem quite right. It returns an error. That isn’t what’s causing your problem but it is strange. It needs to throw the error. There’s also no need for any of the async/await stuff in that function. All that’s doing is unwrapping promises and rewrapping them again. It should be something like this:

deleteProvider (id) {
  return axios.delete('/provider/delete/' + id)
}

That’s all it needs. From the caller’s perspective it will be returning a promise, just the same as if you used async/await.

However, the real problem is inside DeleteProvider.vue. The bit of code I mentioned earlier isn’t using await, it’s using then. If you want to ‘wait’ then you’ll need to move the call to goBack() so that it’s inside the promise chain. Otherwise you could switch it to using await.

I don’t understand the return success and return error parts of that code. They don’t seem to be doing anything. I’ve removed them from my version:

async deleteProvider(id){
  const c = confirm("Do you really want to delete it? You will not be able to restore this data again!")
  if (c) {
    await this.delete(id)
    this.goBack()
  }
},

Likewise, you’ve made the delete method unnecessarily complicated. Immediately returning an awaited value is a promises anti-pattern. A bit like with provider.service.js, you’re unwrapping a promise just to wrap it again. It can be reduced down to:

delete (id) {
  return ProviderService.deleteProvider(id)
}

No need for async/await at all.

If any of this isn’t clear I suggest you do some background reading about promises and async/await. Your code suggests that you don’t really understand them.

1 Like

thx for that @skirtle I really appreciate that … I will go again through my code and apply your suggestions (hopefully it will work)…

anyway - you are right, I am bit confused about async/await and promises - I watched several tutorials (udemy), read a few articles on this topic - that’s why I am practicing it now with this application :slight_smile: and while it’s a bit clearer now, I am still not confident there at all :frowning: would you have any article or resource I should look into pls?

Again, thx a lot - appreciate it