Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to show a element immediately, such as a loading indicator, with Vue? nextTick isn't working as I expect

When I have a Vue component in a .vue file with a data member isLoading: false, and a template:

<div v-show="isLoading" id="hey" ref="hey">Loading...</div>
<button @click="loadIt()">Load it</button>

And a method:

loadIt() {
  this.isLoading = true
  this.$nextTick(() => {
    console.log(this.$refs.hey)
    // ...other work here that causes other DOM changes
    this.isLoading = false
  })
}

("Loading" here refers to loading from the in-memory store, not an AJAX request. I want to do this so that I can show a simple "loading" indicator instantly while the DOM changes that might take 0.2-0.5 second or so are occurring.)

I thought that the $nextTick function would allow both the virtual and actual DOM to update. The console log reads that the item was "shown" (removing the display: none style). However, in both Chrome and Firefox, I never see the "Loading..." indicator; the short delay happens, and the other DOM changes happen without the Loading indicator being shown.

If I use setTimeout instead of $nextTick, I will see the loading indicator, but only when the other work is sufficiently slow. If there is a delay of a few tenths of a second, the loading indicator never shows. I'd like it to appear immediately on click so that I can present a snappy GUI.

Example in a fiddle

like image 875
Patrick Szalapski Avatar asked Dec 10 '18 14:12

Patrick Szalapski


2 Answers

According to this Github 'issue' there is some 'funky' business going on with Vue.nextTick. It appears that the nextTick function actually fires just before the DOM is about to re-render. nextTick will be fired before the DOM can show the loading div and immediately gets hidden once nextTick finishes. There are some suggetions in the Github issue but not all of them work.

The only way they got it to work is if you use setTimeout with a delay. Setting the delay to 1 millisecond won't always guarantee a DOM update, so it is suggested to use around 25 milliseconds. I know this isn't really what you wanted but it's all I could find. Hopefully you get some use out of the Github link.

JSFiddle with examples

like image 191
T. Dirks Avatar answered Nov 09 '22 19:11

T. Dirks


I'm pretty sure you're over complicating things.

This appears to work just fine:

new Vue({
  el: "#app",
  data: {
   isLoading: false,
   done: false
  },
  methods: {
    loadIt() {
      this.isLoading = true
      // Simulate some long running AJAX request...
      window.setTimeout(() => {
        this.done = true
        this.isLoading = false
      }, 3000)
    }
  }
})

Edit: After reading further about your issue, the code below appeared to work for me. It's kind of a hack really. I guess your issue is to do with the event loop?

new Vue({
  el: "#app",
  data: {
   isLoading: false,
   done: false
  },
  methods: {
    loadIt () {
      this.isLoading = true

      window.setTimeout(() => {
        for (var i = 1; i < 40000000; ++i) {
          this.done = !i
        }

        this.done = true
        this.isLoading = false
      }, 10)    
    }
  }
})

Note: This 100% works but I did notice that JSFiddle is a bit odd with 'saving' and 'running'. Make sure to hit Command / Ctrl + S first and then click the Run button.

like image 39
Michael Giovanni Pumo Avatar answered Nov 09 '22 18:11

Michael Giovanni Pumo