Svelte tick() not forcing updates to variables

Viewed 535

I have run into what seems like a common problem in svelte but haven't found a good solution. I have a function that is called when a button is pressed in a component, the basic situation is shown below.

<script>
import tick from 'svelte'

let x = 0;
let y = 0;
let z = 0;
async function buttonHandler() {
     --- multiple computationally intensive loops 
        that update the vars x,y,z at different points ---
    e.g.
    for (a in arr) {

        x ++;
        await tick();
    }
}
</script>

<main>
    <button on:click={buttonHandler}>Click Me</button>
    <p>{x}</p>
    <p>{y}</p>
    <p>{z}</p>
</main>

I basically wanted the x, y, z values to update on screen as the function incremented them, to have live updates of the progress. Originally they were not updating until the function completed, and I read that it was because svelte batch updates changes to reactive variables. I then read that the 'tick()' function was designed for this situation, to 'flush' the updates and have the DOM rerender the updated values. However this still isn't working, the only way I can get it work is by replacing tick() with a generic sleep() function:

function sleep(millisec = 0) {
        return new Promise((resolve, reject) => {
        setTimeout(_ => resolve(), millisec);
        });
    };

This doesn't seem like its a good solution, and I feel like I must be missing something. Have any others had situations where tick isn't working as expected?

1 Answers

You can use the afterUpdate function to check that you flush as expected

import { tick, afterUpdate } from 'svelte';

afterUpdate(() => {
//This should log multiple times, one for every expected value change after tick()
 console.log(x, y ,z);
})

I tried your code in REPL, added basic loops that update every 100x loop, and it still looks instant, but the logs show it did update correctly. So the flush does not trigger a new render like in React (blocks the thread), instead, it has its own batched schedule. To work around this and keep things reactive, and not looking clunky they suggest using CSS animations to get the desired smooth effect. CSS animations don't block the main thread.

"When you update component state in Svelte, it doesn't update the DOM immediately. Instead, it waits until the next microtask to see if there are any other changes that need to be applied, including in other components. Doing so avoids unnecessary work and allows the browser to batch things more effectively."

from: (https://svelte.dev/tutorial/tick)[https://svelte.dev/tutorial/tick]

So this is why it looks instant in your case.

You can see here how they solved this with a tween animation:

https://svelte.dev/tutorial/tweened

So they slow things down with animations which does NOT slow down or block the main thread like setTimeout, setInterval or a dom render does.

UPDATED better example here: https://svelte.dev/repl/c2856360456c40e98ace08438e5bf82f?version=3.38.2

Related