How to subscribe to two or more Svelte stores?

Viewed 908

Given the case that I have two stores and a function, that should run every time one of the stores changes

export const s1 = writable('store1')
export const s2 = writable('store2')

export function onStoreChange(triggeredFrom) {
    console.log('a store has changed! - ', triggeredFrom)
}

Ways I know to handle this 'double subscription' would be inside a .svelte file

$: $s1, $s2, onStoreChange('component')

and inside a javascript file with subscribing to the two stores seperately

const unsubS1 = s1.subscribe(v => {
    onStoreChange('s1 subscription')
})

const unsubS2 = s2.subscribe(v => {
    onStoreChange('s2 subscription')
})

but I'm wondering if there's a more concise way to this, combining the two (or more) subscriptions inside a .js file?

I thought about (mis)using a derived store

export const derivedStore = derived(
        [s1, s2],
            ([$s1, $s2]) => {                 
                    onStoreChange('derived Store', $s1, $s2)
            }
)

but in this case the store value isn't used anywhere and because of that it looks like the derived store doesn't 'survive' compiling - even if the value must have changed, the function inside doesn't run. Thanks to @voscausa for pointing out, that "a store will only run if it has a subscriber" - so the functionality of a derived store doesn't match directly to what I'm looking for but could be complemented with a subscription like this

export const derivedStore = derived(
        [s1, s2],
            ([$s1, $s2]) => [$s1, $s2]
)

const unsubDerivedStore = derivedStore.subscribe(value => {
   onStoreChange('derived Store', ...value)
)

Is this the best way for such a 'multi subscription' or is there an alternative?

a REPL

(Background behind the question is not only to maybe write less code, but to have both values 'directly available' without using get())

$: $s1, $s2, onStoreChange($s1, $s2)

// vs

const unsubS1 = s1.subscribe(s1 => {
    onStoreChange(s1, get(s2))
})

const unsubS2 = s2.subscribe(s2Value => {
    onStoreChange(get(s1), s2)
})

2 Answers

This is where you can use a derived store like

const s3 = derived([s1, s2], ([$s1, $s2]) => [$s1, $s2]);

Store s3 returns an array with the store latest store values from both if one of these stores changes.

And you can create a derived store in a js file. The $ prefix here is to make clear it's the store value and not the store.

So this will work as well:

const s3 = derived([s1, s2], ([v1, v2]) => [v1, v2]);

I tested my code and it works fine.

s3 has to comeup with a new value every time s1 or s2 changes!
A store will do nothing if it's returned value does not change.

The array returned by s3 makes sure s3 will change every time s1 or s2 changes. But there is no need to use the array result in App.svelte. It's not used.

So here is my App.svelte:

<script>
    import {s1, s2, s3} from "./stores.js";
    let i = 0;

    function fn() {
        console.log("s3 triggered fn"); 
    }

    $: if ($s3) fn(); 
</script>

<h1>Test derived</h1>

<button on:click={() => {
 i += 1;
 $s1 = i;
}}>
    button-s1
</button>

<button on:click={() => {
 i += 1;
 $s2 = i;
}}>
     button-s2
</button>

And the stores.js

import {writable, derived} from "svelte/store"

export const s1 = writable(0);
export const s2 = writable(0);
export const s3 = derived([s1, s2], ([$s1, $s2]) => {
    console.log("derived", $s1, $s2);
    return [$s1, $s2];
});
Related