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

In the past I’ve been able to work with DOM elements using mounted() and $nextTick() but in my current project the component rendering isn’t complete when $nextTick runs.

	mounted() {
		this.$nextTick(() => {
			console.log(document.querySelectorAll(".dropdown-button"));
		});
	},

This reports “NodeList []” in the console. But if I try the DOM query in the console after the page loads I get “NodeList [a.dropdown-button]”

I am using vue-router if that has any effect on this.

1 Like

Better to use the ref attribute rather than querySelector. https://vuejs.org/v2/api/#ref

refs are only populated after the render is complete, so it doesn’t help with the problem that I’m having.

Isn’t that what you want? To access the element once the component has rendered?

Correct, the problem is that the way I would normally wait for the render to complete to access either the DOM or $refs isn’t working.

Ok. But you haven’t really provided the whole picture. You’ll need to share the related code to your problem for us to see what’s going on

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.

3 Likes

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.

1 Like

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.

ok for some reason the editor won’t allow me to show the html but it basically calls a v-if and the .ts method is what is called on the v-if

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.

Please read this on how to properly post code / markup:

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 finall understand your real problem. You misunderstand the lifecycle of a component / Vue instance.

Take a look at this 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.

Yes, that did it. I see nodes. I see nodes. I had to use the updated after the mounted version of your response but it works. (I’m not that well versed with promises as I should be especially with Typescript) but the solution works and thank you. Mind you, I did see that diagram, but I guess I misunderstood at the point of the mounted, because I originally placed the loadData method on the created lifecycle hook thinking that I could manage the DOM at mounted but that didn’t work for me either but after your explanation now I get it and it works. Thank you very much. I appreciate you hanging in there with me.

Thank you so much! I had the same problem, you saved my day!