Skip to content

How to wait for a browser re-render? Vue.nextTick doesn't seem to cover it, and setTimeout(..., 0) is not good enough. #9200

@szalapski

Description

@szalapski

What problem does this feature solve?

Example in a fiddle: https://jsfiddle.net/szal/eywraw8t/500316/ - when I click Load, I never see the "Loading..." indicator.

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 synchronous 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.

My unsatisfying solution: I have to wrap the work in a setTimeout(..., 25), per seebiscuit's findings here: vuejs/vuex#1023 (comment)

This seems to reveal that the documentation on Vue.nextTick at https://vuejs.org/v2/api/#Vue-nextTick is inaccurate: "Defer the callback to be executed after the next DOM update cycle. Use it immediately after you’ve changed some data to wait for the DOM update." This seems wrong; this is a good case where the user doesn't see the DOM update at all even though Vue.nextTick was used.

Calling setTimeout(..., 25) doesn't seem very discoverable or intuitive for anyone.

What does the proposed API look like?

I'd propose either a change to Vue.nextTick to work better, or a parallel call just like Vue.nextTick, perhaps "nextRender" (maybe there's a better name?) that ensures that any DOM changes can get displayed to the user before its callback is invoked asynchronously. So then I could do something like:

methods: {
 async selectThing() {
  this.thingIsLoading = true  // this triggers a dead-simple v-show
  await this.$nextRender() // the new Vue API call; give browser a chance to paint the loading indicator
  const route = setQuery(this.$route, 'thingid', this.thingViewModel.thing.id)
  this.$router.push(route) // this triggers lots of reactivity that may take a big fraction of a second to rerender/paint
  this.thingIsLoading = false
 }
}

If others feel that instead an enhancement to the behavior of Vue.nextTick is in order, I'd prefer that.

I'd also propose changing the documentation at https://vuejs.org/v2/api/#Vue-nextTick to be more accurate and perhaps explain this limitation and a workaround to future developers.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions