Corrl
Corrl

Reputation: 7699

How to subscribe to two or more Svelte stores?

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)
})

Upvotes: 1

Views: 3522

Answers (3)

Poulpynator
Poulpynator

Reputation: 1176

If you want to subscribe to multiple stores without using derived and want to use it in a JS file you can use something like this :

function subscribeMultiples(stores, callback)
{
    // Store values of all the stores
    const values = [];
    const unsubscribes = [];
    // Subscribe to all the stores
    for (let i = 0; i < stores.length; i++)
    {
        unsubscribes[i] = stores[i].subscribe((value) => {
            values[i] = value;
            // Call the callback when all the stores have values
            if (values.length == stores.length) callback(values);
        });
    }

    return () => {
        unsubscribes.forEach((unsubscribe) => unsubscribe());
    }
}

Which you can use like so :

subscribeMultiples([s1, s2], ([$s1, $s2]) => {
    console.log("subscribeMultiples", $s1, $s2);
});

It's a simplified version of how derived works.

Upvotes: 1

voscausa
voscausa

Reputation: 11706

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];
});

Upvotes: 0

voscausa
voscausa

Reputation: 11706

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]);

Upvotes: 4

Related Questions