Search code examples
javascriptjavascript-objectssveltesvelte-componentsvelte-store

Detect when svelte store is not used anymore


I'm making a custom svelte store by wrapping around a svelte writable store.

I want to detect when that store is not subscribed by any component; when the subscription count is 0

My objective is to clear some heavy external resources (websockets) that were tied to the custom store when no one is using it.

Currently, I'm counting the subscriptions and unsubscriptions by wrapping around subscribe( ) method. It works as expected. But It looks like a nasty hack to me.

My question: Is there a standard / clean way to achieve this behavior in Svelte?

If not, can someone with more experience in Javascipt and svelte confirm whether this is legit?

Demo on : https://svelte.dev/repl/f4e24fb5c56f457a94bf9cf645955b9f?version=3.43.1

import { writable } from 'svelte/store';

// Instanciate the store
export let store = MakeStore();

// By design, I want a function that returns a custom svelte store
export function MakeStore(initialValue = null) {

        const { subscribe, set, update } = writable(initialValue);

        let subscribercount = 0;

        let wsubscribe = function (run, callback) {
            subscribercount++;
            console.log("subscribercount++", subscribercount);

            let wunsubscribe = subscribe(run, callback);
            return () => {
                subscribercount--;
                console.log("subscribercount--", subscribercount);

                if (subscribercount == 0) {

                    // -------------------------------
                    // Free up resources
                    // I want a clean way to get here
                    // -------------------------------
                    console.log("Cleaning up...");
                }
                return wunsubscribe();
            }
        }

        // Some external calls here

        let store = {
            subscribe: wsubscribe,
            set: newvalue => {
                set(newvalue);
                // Some external calls here
            },
            update: update
        };

        // Some external calls here

        return store;
    }



Solution

  • Yes, it's built into the store and documented here

    from the docs

    If a function is passed as the second argument, it will be called when the number of subscribers goes from zero to one (but not from one to two, etc). That function will be passed a set function which changes the value of the store. It must return a stop function that is called when the subscriber count goes from one to zero.

    so you would do for example:

    const count = writable(0, () => {
        console.log('got a subscriber');
        return () => console.log('no more subscribers');
    });
    

    Update 8 Feb 2023

    Note that the above works for both readable and writable stores, in the case of derived stores where you would have the following code:

    const count = derived(items, ($items, set) => {
      console.log('got a subscriber to a derived store');
      return () => console.log('no more subscribers to derived store');
    });
    

    Here it will log no more subscribers to derived store, when either the number of subscribers drops to 0 or when the original store changes (this is because this entire function ($items, set) => {...} runs again).

    As of v3.55.1 there is no built in way to circumvent this.