How to not trigger svelte store subscription if the value was not changed

Viewed 540

I'm trying to figure out how to work with Svelte stores properly. In my code I have a store that it's initial value is either come from localStore if set or from const, I never called set or update on that store without some action from the user. In other component there is a subscriber for that store that doing server request in each change (I want the request to happen only if the store changes), however I notice that on app init the request is fire (the subscription callback is called)

Looking at the docs here https://svelte.dev/tutorial/writable-stores

count.subscribe(value => {
    countValue = value;
});

I can see that the subscribe callback is running once even before I clicked any button. How can I subscribe only to store changes (considering setting default value I pass to writeable is not "change")?

1 Answers

You'd have to build some utility for yourself. It's pretty straightforward when you know Svelte's stores contract, which is itself pretty tiny.

Something like this would work:

import { writable } from 'svelte/store'

const events = store => ({
    ...store,
    subscribe(subscriber) {
        let initial = true
        
        const unsubscribe = store.subscribe($count => {
            if (!initial) {
                subscriber($count)
            }
        })
        
        // the init call of the subscriber is made synchronously so, by
        // now, we know any further call is a change
        initial = false
        
        return unsubscribe
    }
})

export const count = writable(0)

// countEvents will only call its subscribers for changes that happen
// after the call to subscribe
export const countEvents = events(count)

You could then use this "event store" just normally. For example (REPL):

(Notice that $countEvents is undefined until the underlying count stores actually changes, since countEvent's subscribers are not called on subscribe.)

<script>
    import { onMount } from 'svelte'
    import { count, countEvents } from './stores'
    
    const increment = () => count.update(x => x + 1)
    const decrement = () => count.update(x => x - 1)
    
    let logs = []
    
    onMount(() => {
        return count.subscribe((value) => {
            logs = [...logs, value]
        })
    })
    
    let changeLogs = []
    
    onMount(() => {
        return countEvents.subscribe((value) => {
            changeLogs = [...changeLogs, value]
        })
    })
</script>

<p>
    <button on:click={decrement}>-</button>
    <button on:click={increment}>+</button>
    {$count} / {$countEvents}
</p>

<table>
    <caption>Calls</caption>
    <thead>
        <tr>
            <th>subscribe</th>
            <th>subscribeChanges</th>
        </tr>
    </thead>
    {#each logs as log, i}
        <tr>
            <td>{log}</td>
            <td>{changeLogs[i] ?? ""}</td>
        </tr>
    {/each}
</table>
Related