Svelte Derived Store atomic / debounced updates

1.8k views Asked by At

I have been experimenting with svelte derived stores. If you look at the console log in the following example, after incrementing store 1 and store 2, then hitting reset, the derived store emits 2 updates.

How would you make this atomic or perhaps debounce it? For example, lets say the scenario were that the derived store issued a new network request based on the combined values of store 1 and 2, if they both changed together you wouldn't want to fire 2 network requsts?

https://svelte.dev/repl/a67c9d37aee348d988e8c30f60a139d9?version=3.28.0

EDIT: Actually, just added a 'reactive' console log and this appears to debounce (tick?) the output.

I think I have answered my own question?

2

There are 2 answers

0
Tan Li Hau On BEST ANSWER

The derived store's callback 2nd argument is set, which allows setting the derived value asynchronously.

import { derived } from 'svelte/store';

const delayed = derived(a, ($a, set) => {
    setTimeout(() => set($a), 1000);
});

$: console.log($delayed);

$a = 1;
// console '1' 1 second later.

API reference for derived()


For your case, you could call a debounce function that will eventually call set:

// implementing debounce yourself
let timeoutId;
const total = derived([counter1, counter2], ([c1,c2], set) => {
    if (timeoutId) {
        clearTimeout(timeoutId);
    }
    timeoutId = setTimeout(() => {
        console.log(c1, c2);
        return set(c1+c2);
    }, 1000);
}, 0);

Svelte REPL

0
mmoufou On

Adding onto @TanLiHau's answer, you can also do this without the "global"/outer scope variable timeoutId.

Svelte's derived allows to return a callback for cleaning up (https://svelte.dev/docs#derived):

If you return a function from the callback, it will be called when a) the callback runs again, or b) the last subscriber unsubscribes.

This allows you to write it as follows:

const total = derived([counter1, counter2], ([c1,c2], set) => {
    const timeoutId = setTimeout(() => {
        console.log(c1, c2);
        return set(c1+c2);
    }, 1000);
    
    return () => clearTimeout(timeoutId)
}, 0);