Cannot access element shown by v-if in component mounted callback

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

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.

 

szalapski commented on Dec 15, 2018

But Vue.nextTick is supposed to overcome this, according to the documentation. It seems the only way around it is to do seebiscuit's unintuitive setTimeout(...,25) solution, or by adding other delays as you suggest. I think Vue.nextTick should be enhanced to address this scenario--it is the call that is supposed to give the browser a chance to repaint the screen.

It sounds like Vlad and Posva are saying "too bad, we expect the developer to figure out all the details under the covers and do the obscure workaround." Not a very friendly API, and it contradicts the documentation. You are building a hill of pain rather than a pit of success.

 

twickstrom commented on Sep 2, 2019

Ran across this issue today. Put together a little NPM package to use the double RequestAnimationFrame method that was suggested by @Justineo . Thank you for the suggestion!

This will allow you to do: this.$forceNextTick(() => { //your code here })

https://www.npmjs.com/package/vue-force-next-tick

 

 

 

$nextTick in mounted() not working for waiting on component render

LinusBorg

If the element isn’t showing up in your $refs after a nextTick() in mounted(), then you have a problem in your component or your business logic, so you will have to share the involved template & component code.

I would guess that you have some v-if somewhere that keeps the element from rendering because the condition relies on some async data and that isn’t available yet - or something in that direction.

 

jsodeman

YES, that’s the issue. It’s a SPA and I’m using vuex to control the visibility of parts of the page during component loading. In this case I have <div v-if="!$store.state.loading"> wrapping the component template.

Moving my this.$store.commit("loaded"); to before the nextTick instead of inside it fixed the problem on the child components. The root component has some other store related v-if statements that I need to figure out but that puts me on the path at least.

 

I have a similar problem in Core 2 Typescript environment, but how can you claim its business logic if your depending on a hook determining when the view has been rendered. I don’t get the confusion. With jQuery, I have had similr code and ready doesn’t trigger until the page is rendered. It shouldn’t matter what it is that I’m doing (and for the record, I am using v-ifs on each field per row to determine if the field should be shown) if its a tool of the system it should work accordingly. Otherwise even then its still a guess as to the timeline. It could be a lot of data. The point is that when rendering is done, I should be able to access the DOM and I cannot. Unfortunately I cannot share the code for privacy reasons

But an example of 1 of 30 fields in the row:
“td class=“left-justify td-base” v-if=“checkField(‘fieldName’)”>field td”

in .ts
checkField(fieldname: string) {
return this.secMgrClass.CheckField(fieldname.toLocaleLowerCase());
}

Not only do I have a method that depends on the rows being loaded, but I have another that need to run on resize after being loaded.

 

 

I would love to see how you render a form with jQuery (or any other framework) that only shows field who’s data has been loaded without some sort of conditional check in your code or template, and then automatically updates after it arrives.

When I said its “business logic” what I meant was this: you as the developer have to decide what happens when some data is not there yet - should the whole component not be rendered? should only a field not be rendered? should a default value be used in place of the missing data? or some completely other element /chunk of html?

This decision is not something a tool can do for you because it’s entirely dependent on the way that you want the app to behave in that situation.

 

True, but my point is that I’ve not had this problem with view not being rendered when I run

$(document).ready(function(){
       this.codeHere()
}); 

the Dom is has always been populated. Now of course I run conditional check but that’s been for sake of validation of the data than checking to see if its been rendered yet. I don’t place the code in some sort of wait state until the document has rendered. I’ve never needed to.

Background: My Environment
I’m in a Core 2, Typescript, VueJs environment and I’ve discovered that I’ve had to piece together documentation from multiple sources and take my best guess as to how to implement some of the directions. I wonder that since I extend Vue to create my component using _vue-property-decorator** that something with nextTick has been lost in translation… this is all running in the mounted() lifecycle hook

mounted(){
     this.loadData(); ///fetch the data and calls the method that returns the data that is used to loop through the data to build the rows

     this.$nextTick(()=>{
            this.manipulateDom(); ///is supposed to run after the looping process is completed, but always seem to run just before starts... then the looping is done in a split second
}):

Now, according to my experiences thus far, and I am very new to VueJs and TypeScript, I seem to be missing something I’ve not found in the normal documentation. My assumption was that nextTick runs after rendering (in association with mounted) so, similar to the jQuery shown above I could count on the view being rendered when nextTick runs. This is the promise the documentation seems to make that has yet to work out for me… now if I have to go underground and poll to see if all’s been rendered before I can access the DOM then that’s one thing… my guess is that I’m not doing some thing correct and that’s the quest that brought me here.

 

Thanks for the brackground, now I finally understand your real problem. You misunderstand the lifecycle of a component / Vue instance.

Take a look at this 363 in another tab as a reference, then read on:

If you look at that diagramm, you will hopefully see that mounted is called when the component has been created and rendered for the first time.

Looking back at your code, that means that you call this.loadData() after the component has already been rendered for the first time, and it has been rendered without the data that you are about to load just now. So right now, there are no “rows” to select or manipulate in the DOM.

That’s why your $nextTick doesn’t help, either. It only pushed execution to the next callstack, but that still runs long before the async loading of data that you initialized in the line before has returned, and consequently, it runs long before the DOM has been updated with the new elements that are rendered with the data.

So what happens instead? Once the data returns after possibly a couple of 100ms, loadData propbably updates the component’s data, which triggers a re-render, which results in new DOM nodes being created with that data, as defined in your template (see the update cycle next to mounted in the diagram).

So what you really should be doing it to wait for the data loading to finish (which will make the component re-render and create the new DOMelements, your “rows”, and then you can manipulate those elements. You would still use $nextTick to ensure that the new elements have been inserted into the document (which happens asynchronously).

What does that look like?

mounted() {
  // this assumes ``loadData` returns a Promise 
  // that resolves after local data has been updated
  this.loadData().then(this.$nextTick).then(() => {
    this.manipulateDOM
  })
}

I passed this.$nextTick to the first .then() because it returns a promise resolving on the next tick, thereby ensuring th DOM has been updated after the data has been loaded. A more Verbose version would look like this:

mounted() {
  this.loadData().then(() => {
    this.$nextTick(() => {
      this.manipulateDom()
    })
  })
}

You could also use the updated hook, but that would call the manipulateDOM after any re-render, which might be done because some other data in your component changed, so it’s probably not what you want:

mounted() {
  this.loadData()
},
updated() {
 this.$nextTick(() => {
    this.manipulateDom()
  })
}

I also want to offer an attempt to compare this to jQuery, in hopes that it hels you make the connection more easily.

// the get call is you `loadData`
jQuery.get('/users', function(data) {
  
  // here you would probably use $() to create new DOM elements from the data that you received, right?
  // that's the part that Vue does for you after loading data has finished.
  createDomFromData(data)
  // only *after* you have done that, you would try and manipulate thje DOM you just created:

  manipulateDom()
})

Vue’s createDomFromData is asynchronous, which is why you have to use $nextTick() if you need a guarantee that the generated DOM actually be in the document. but $nextTick doesn’t wait for your data loading to finish.

 

Been struggling with logging refs object for 3 days…Thanks for this detailed explanation Linus. Really helped me… I also used the updated life cycle method cause I had a v-if controlling the loading of a table…and updated method will run after the v-if and hence my refs was available on console.log

 

Does nextTick() work weirdly?

Can somebody explain the behavior of the code in the following fiddle?

JS Fiddle 70

I have always though, that nextTick() callback will be fired after next round of updates will be finished and the app will be fully synchronized with the state.
But we cant see value “B” in the DOM.

 

In this fiddle, you can see that the DOM was actually updated with “B” correctly, but the page wasn’t repainted yet:

https://jsfiddle.net/Linusborg/n9jmu5v7/24098/ 187

this is because the the dom patching by Vue is done in a microtask on the same callstack, and the nexttick pushed another task on that microtask queue - which will also run before the browser can actually do a re-paint after the current callstack has finished.

Using setTimeout() with a couple ms delay on the other hand creates a macro task, pushing execution to the next callstack and gives the browser a brief window of time to do the repaint.

https://jsfiddle.net/Linusborg/n9jmu5v7/24101/ 181 (using 60ms here)

setImmediate() has the same effect, but has to be polyfilled in IE if I recall correctly?

https://jsfiddle.net/Linusborg/n9jmu5v7/24103/ 114

That’s as far as I myself understand it. the event loop & callstack are a fickle beast.

 

 

 

Cannot access element shown by v-if in component mounted callback

Vue renders to the DOM asynchronously. So, even though you are setting your loaded property to true, the ref will not exist until the next tick in Vue's cycle.

To handle that, use the $nextTick method.

 

vuejs mounted is called even if component was not loaded via v-if

复制代码
console.clear()

new Vue({
  el: "#app",
  data:{
    loading: true
  },
  mounted(){
    setTimeout(()=> {
      this.loading = false
      this.$nextTick(() => console.log(this.$refs.done))
    }, 1000)
  }
})
复制代码

 

<script src="https://unpkg.com/vue@2.4.2"></script>
<div id="app">
  <div v-if="loading">Loading</div>
  <div ref="done" v-else>Done</div>
</div>

 

https://v3.vuejs.org/api/options-lifecycle-hooks.html#mounted

Note that mounted does not guarantee that all child components have also been mounted. If you want to wait until the entire view has been rendered, you can use vm.$nextTick inside of mounted:

mounted() {
  this.$nextTick(function () {
    // Code that will run only after the
    // entire view has been rendered
  })
}

 

What is nextTick and what does it do in Vue.js?

The key concept to understand is that the DOM is updated asynchronously. When you change a value in Vue, the change is not immediately rendered to the DOM. Instead, Vue queues a DOM update and then, on a timer, updates the DOM. Normally, this happens so fast that it doesn't make a difference, but, at times, you need to update the rendered DOM after Vue has rendered it, which you can't immediately do in a method because the update hasn't happened yet. In those cases, you would use nextTick. Documented here.
– Bert
Dec 4 '17 at 14:34

 

回答1

It's all about Timing

nextTick allows you to execute code after you have changed some data and Vue.js has updated the virtual DOM based on your data change, but before the browser has rendered that change on the page.

Normally, devs use the native JavaScript function setTimeout to achieve similar behavior, but using setTimeout relinquishes control over to the browser before it gives control back to you (via calling your callback).

Example

Let's say you changed some data; Vue then updates the vDOM based on that data change (the changes are not yet rendered to the screen by the browser).

If you used nextTick at this point, your callback would get called immediately, and the browser would update the page after that callback finished executing.

If you instead used setTimeout, then the browser would have a chance to update the page, and then your callback would get called.

You can visualize this behavior by creating a small component like the following:
(Check this fiddle to see it live)

<template>
  <div class="hello">
    {{ msg }}
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data() {
    return {
        msg: 'One'
    }
  },
  mounted() {
      this.msg = 'Two';

      this.$nextTick(() => {
          this.msg = 'Three';
      });
  }
}
</script>

Run your local server. You will see the message "Three" being displayed.

Now, replace this.$nextTick with setTimeout:

setTimeout(() => {
    this.msg = 'Three';
}, 0);

Reload the browser. You will see "Two" before you see "Three".

That's because, with setTimeout:

  1. Vue updated the vDOM to say "Two"
  2. Vue gave control to the browser
  3. The browser displayed "Two"
  4. Callback was called
  5. Vue updated the vDOM to say "Three"
  6. Vue gave control to the browser
  7. The browser displayed "Three"

But with nextTick, we skip steps 2 and 3! Instead of passing over control after the first vDOM update, Vue calls the callback immediately, which prevents the browser from updating until the callback is finished. In this example, that means "Two" is never actually displayed.

To understand how Vue implements this, you need to understand the concept of the JavaScript Event Loop and microtasks.

Once you have those concepts clear(er), check the source code for nextTick.

 

回答2

Next Tick basically allows you to run some code, after the vue has re-rendered the component when you have made some changes to the a reactive property (data).

// modify data
vm.msg = 'Hello'
// DOM not updated yet
Vue.nextTick(function () {
   // this function is called when vue has re-rendered the component.
})
    
// usage as a promise (2.1.0+, see note below)
Vue.nextTick()
   .then(function () {
       // this function is called when vue has re-rendered the component.
    })

From the Vue.js Documentation:

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.

Read more about it, here.

 

回答3

To make Pranshat's answer about the difference between using nextTick and setTimeout, more explicit, I have forked his fiddle: here

mounted() {    
  this.one = "One";
     
  setTimeout(() => {
    this.two = "Two"
  }, 0);
      
  //this.$nextTick(()=>{
  //  this.two = "Two"
  //})}
}

You can see in the fiddle that when using setTimeOut, the initial data flashes very briefly once the component gets mounted before adapting the change. Whereas, when using nextTick, the data is hijacked, changed, before rendering to the browser. So, the browser shows the updated data without even any knowledge of the old. Hope that clears the two concepts in one swoop.

 

Vue.nextTick 的原理和用途

简单理解Vue中的nextTick

Vue中$nextTick,created和mounted的官方解释

在这里插入图片描述

 

作者:Chuck Lu    GitHub    
posted @   ChuckLu  阅读(98)  评论(0编辑  收藏  举报
编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
历史上的今天:
2020-01-07 ssms18清除记住的账号和密码
2019-01-07 uniqueidentifier in SQL becomes lower case in c#
2016-01-07 VS2015中添加新建项,找不到ado .net entity datamodel的解决方法
2016-01-07 vs2015中安装EntityFramework
点击右上角即可分享
微信分享提示