4

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) // virtual DOM console.log(document.getElementById('hey')) // actual DOM // ...other work here }) } 

I thought that the nextTick function would allow both the virtual and real DOM to update, and thus the two console.log lines would output the same results. However, they do not: it seems that the real DOM isn't being updated right away, and thus the second log results in a element with display: none; whereas the first log doesn't--I get this on the console:

<div id="hey" data-v-964d645e="" style=""> <div id="hey" data-v-964d645e="" style="display: none;"> 

(By the way, even if I use setTimeout instead of this.$nextTick, I get the very same results from console.log. I also tried using the updated hook, but the same symptoms happen there. If I code any variation in a .js file, the problem goes away, but it persists when in a .vue file.)

Is there some kind of optimization or further asynchrony in how Vue updates the actual DOM from the virtual DOM? How do I get the actual DOM to update right away?

4
  • 1
    Note that console can update the display after the fact (usually see an info icon). To be precise, you should save the state of this.$refs.hey.getAttribute("style") for an accurate snapshot. Does not negate your issue, though. Commented Dec 7, 2018 at 22:53
  • 1
    I can't reproduce on Codesandbox. Are you able to alter it to reproduce? Commented Dec 7, 2018 at 22:55
  • 1
    this.$refs.hey is not a virtual DOM element; it's an actual DOM element Commented Dec 8, 2018 at 0:43
  • I think all of you are right. Thanks for helping me find the problem. My follow-up is here: stackoverflow.com/questions/53707415 Commented Dec 10, 2018 at 14:10

3 Answers 3

3

This works pretty much as expected, with messages matching both before and after DOM update.

I think your understanding that the refs call returns a virtual node rather than an actual DOM element is incorrect.

new Vue({ el: '#app', data: { isLoading: false }, methods: { loadIt() { this.isLoading = true; this.tellMeAboutIt('before'); this.$nextTick(() => this.tellMeAboutIt('after')); }, tellMeAboutIt(when) { console.log(`Virtual ${when}:`, this.$refs.hey) // virtual DOM console.log(`Actual ${when}:`, document.getElementById('hey')) // actual DOM } } });
<script src="https://unpkg.com/vue@latest/dist/vue.js"></script> <div id="app"> <div v-show="isLoading" id="hey" ref="hey">Loading...</div> <button @click="loadIt()">Load it</button> </div>

Sign up to request clarification or add additional context in comments.

5 Comments

I'm finding the strange behavior only when it is in a .vue file, but not when in a pure .js setting. Any ideas?
I've confirmed that the two different calls still return different results, even if called again: console.log(this.$refs.hey, document.getElementById('hey')); console.log(this.$refs.hey, document.getElementById('hey')) returns the elements with different styles, twice.
Interesting. On non-.vue file, the results are the same object; obviously not true for .vue files. Try this.$forceUpdate() after you set isLoading to see if you get the result you want.
$forceUpdate didn't help; behavior is the same.
Thanks for helping me find the problem. My follow-up is here: stackoverflow.com/questions/53707415
1

Please review Vue lifecycle in the documentation. Of note is that there is a disconnect between these two events. Also note that nextTick() waits for the next DOM update cycle, not necessarily the virtual DOM.

This is typically addressed by using the updated lifecycle hook, which executes code after the virtual DOM has already been updated. If you need to execute some code with the guarantee that the virtual DOM has already been updated, you will want to do it there.

You may also be interested in reactivity in depth. This should act as a good complement to the lifecycle diagram.

11 Comments

My mistake. It seemed like you were trying to perform DOM modifications. I'll revise my answer accordingly.
@PatrickSzalapski Please review my updated answer. Sorry for the incorrect assumption!
Great, thanks. That document reads, "In order to wait until Vue.js has finished updating the DOM after a data change, you can use Vue.nextTick(callback) immediately after the data is changed. The callback will be called after the DOM has been updated." Are you saying that part is incorrect?
Also, the updated documentation has this: "Note that updated does not guarantee that all child components have also been re-rendered. If you want to wait until the entire view has been re-rendered, you can use vm.$nextTick inside of updated." Does this contradict what you are saying? I'll try it.
The problem with nextTick() is that this will only work for one level of ancestry in your components. If your child components also have child components and you need to wait for these grandchild components to update, then nextTick() will be insufficient. If you need to await a full component ancestry re-render, you're going to run into some trouble. You should either better separate your components to decouple their functionality, or consider using inter-component communication (e.g. emit(event_name) and v-on:event_name).
|
0

Turns out that problems with either the eslint-loader cache or the babel-loader cache caused this weirdness. I could only fix it by deleting all of node_modules\.cache. Too bad I don't know why it happened in the first place.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.