Vuex: Vue.nextTick doesn't seem to work with Vuex

Created on 27 Oct 2017  路  6Comments  路  Source: vuejs/vuex

Version

2.5.0

Reproduction link

https://jsfiddle.net/n9jmu5v7/2421/

Steps to reproduce

Click the "Change" button.

What is expected?

The value updates to "B" immediately, and then to "C" after a few seconds.

What is actually happening?

The value stays at "A", and updates to "C" after a few seconds.

Most helpful comment

Just a bit confused with your terminology @ktsn. requestAnimationFrame or setTimeout may be used to _wait_ for the DOM to re-render. Not sure how I'd use either to "explicitly re-render the DOM". And in the case that we're just using requestAnimationFrame or setTimeout to wait for the DOM to update, well, that isn't the most reliable method.

But I think I found a pretty good work-around: Do not use requestAnimationFrame. But use setTimeout(callback, 1) where 1 is the delay and it must be delay > 0. For the details keep reading.

Update: Apparently, the delay of 1 will not guarantee that the DOM repainted. I've tried several values and 25 seems to be a more reliable limit. There seems to be some processor dependent stuff going on here, that I cannot comment about that determines how long the DOM may need to actually paint.


To understand how all of these functions actually work, Vue.$nextTick, requestAnimationFrame and setTimeout, I expanded the fiddle provided by @adamzerner, here.

I learned that with regards to the DOM render, setTimeout (0 delay) and Vue.$nextTick seem to behave identically. Both invoke the callback _after_ the DOM renders. To verify this I added a debugger; statement at the top of the callback. In DevTools, when the code breaks, you'll notice the DOM _has_ updated with the intermediate value (for example, 'B') _before_ the callbacks are invoked.

The behavior is very different with requestAnimationFrame鈥擾as per the spec_ (well at least the was MDN documents it), since requestAnimationFrame is deisgned to invoke the callback _before_ the DOM repaints. So, in essence, requestAnimationFrame will update the final value (eg. 'C') _before_ the DOM has a chance to process the change from to the intermediate value ('B').

Still, we have to ask, for the setTimeout (0 delay) and Vue.$nextTick scenarios, shouldn't we see the intermediate value ('B') before the final value ('C') callback is invoked? I think that's a good question and I don't have a definitive answer to that. But it seems like there may be some async behavior in the way the DOM repaints. I think that from the time a command to update the DOM is sent and and the time the DOM actually does the repainting, the callback is invoked and the blocking loop fires off. And since both the blocking loop and the DOM paint processes share the same single-threaded Main thread, DOM repaint is blocked until _after_ the final value is set. Also, explains why we do see the intermediate value in the $nextTick/setTimeout test when the debugger pauses: the break in the debugger frees up the main thread before the loop fires of and let's the DOM continue.

And that's exactly what we see when change the 0 delay in the setTimeout test to 1. Now the intermediate value ('B') is actually painted on the screen before the loop has a chance to start, and hangs there until the loop finished and updates the final value ('C'). The extra ms in the setTimeout gives the DOM update routine just enough time to be invoked _before_ the blocking loop is called. Unfortunately, we can't fine tune Vue.$nextTick to wait just that ms moment, so setTimeout(cb, 1) is out best bet.

Hopes this helps someone :wave:

All 6 comments

This is expected behavior. Also this is reproducable without Vuex. https://jsfiddle.net/n9jmu5v7/2426/

nextTick schedules the callback just before re-render. If you want to explicitly re-render the DOM, use requestAnimationFrame or setTimeout.

Ok, @ktsn. I get what you're saying. But, how do you wait for the DOM to re-render? I need to measure the container size after a value has been updated and the DOM updated. If Vue.nextTick hits right before the DOM is re-rendered, then what is the best strategy for invoking a callback right _after_ the DOM is re-rendered?

As I said in my before comment, If you want to explicitly re-render the DOM, use requestAnimationFrame or setTimeout.

Just a bit confused with your terminology @ktsn. requestAnimationFrame or setTimeout may be used to _wait_ for the DOM to re-render. Not sure how I'd use either to "explicitly re-render the DOM". And in the case that we're just using requestAnimationFrame or setTimeout to wait for the DOM to update, well, that isn't the most reliable method.

But I think I found a pretty good work-around: Do not use requestAnimationFrame. But use setTimeout(callback, 1) where 1 is the delay and it must be delay > 0. For the details keep reading.

Update: Apparently, the delay of 1 will not guarantee that the DOM repainted. I've tried several values and 25 seems to be a more reliable limit. There seems to be some processor dependent stuff going on here, that I cannot comment about that determines how long the DOM may need to actually paint.


To understand how all of these functions actually work, Vue.$nextTick, requestAnimationFrame and setTimeout, I expanded the fiddle provided by @adamzerner, here.

I learned that with regards to the DOM render, setTimeout (0 delay) and Vue.$nextTick seem to behave identically. Both invoke the callback _after_ the DOM renders. To verify this I added a debugger; statement at the top of the callback. In DevTools, when the code breaks, you'll notice the DOM _has_ updated with the intermediate value (for example, 'B') _before_ the callbacks are invoked.

The behavior is very different with requestAnimationFrame鈥擾as per the spec_ (well at least the was MDN documents it), since requestAnimationFrame is deisgned to invoke the callback _before_ the DOM repaints. So, in essence, requestAnimationFrame will update the final value (eg. 'C') _before_ the DOM has a chance to process the change from to the intermediate value ('B').

Still, we have to ask, for the setTimeout (0 delay) and Vue.$nextTick scenarios, shouldn't we see the intermediate value ('B') before the final value ('C') callback is invoked? I think that's a good question and I don't have a definitive answer to that. But it seems like there may be some async behavior in the way the DOM repaints. I think that from the time a command to update the DOM is sent and and the time the DOM actually does the repainting, the callback is invoked and the blocking loop fires off. And since both the blocking loop and the DOM paint processes share the same single-threaded Main thread, DOM repaint is blocked until _after_ the final value is set. Also, explains why we do see the intermediate value in the $nextTick/setTimeout test when the debugger pauses: the break in the debugger frees up the main thread before the loop fires of and let's the DOM continue.

And that's exactly what we see when change the 0 delay in the setTimeout test to 1. Now the intermediate value ('B') is actually painted on the screen before the loop has a chance to start, and hangs there until the loop finished and updates the final value ('C'). The extra ms in the setTimeout gives the DOM update routine just enough time to be invoked _before_ the blocking loop is called. Unfortunately, we can't fine tune Vue.$nextTick to wait just that ms moment, so setTimeout(cb, 1) is out best bet.

Hopes this helps someone :wave:

iOS 12 is still not working set 0 or 1, so I set setTimeout(cb, 100). There's so many problems in iOS 12 :(

I had lots of struggle with this from this question: https://stackoverflow.com/questions/53707415 (Thanks for the good solution, seebiscuit!)

As I said in my before comment, If you want to explicitly re-render the DOM, use requestAnimationFrame or setTimeout.

Per seebiscuit's findings, setTimeout(..., 0) doesn't seem to do this well enough. Should there be another call in Vue that enables this scenario? Vue.afterNextRender, perhaps? Calling setTimeout(..., 25) doesn't seem very discoverable or intuitive.

I've proposed it here: https://github.com/vuejs/vue/issues/9200

Was this page helpful?
0 / 5 - 0 ratings