Skip to content

πŸͺ Simple implementation of a Subscription object that notifies when the value changes.

Notifications You must be signed in to change notification settings

todar/JavaScript-Stores

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

4 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

JavaScript Stores

A simple library to deal with state using a subscription model.

This is fully inspired by svelte stores and is my attempt and replicating them!

Examples

Writable Store

example on codepen.

// Create a `Writable` store that allows both updating and reading by subscription.
const writable = (initialState, onFirstSubscription = null) => {
    let state = initialState;
    let subscriptions = [];
    let onLastUnsubsribe = null;

    // ⚑ Update to new state and notify subscribers.
    const set = value => {
        if (state === value) return;
        state = value;
        subscriptions.forEach(fn => fn(state));
    };

    // 🍌 => 🍌 + 🍍 Update state using a callback function and notify all subscribers.
    const update = fn => {
        set(fn(state));
    };

    // 🎫 Add subscripiton and immediatly run with the current state.
    const subscribe = run => {
        subscriptions = [...subscriptions, run];
        
        // Pass `Set` for readonly stores to update the state.
        if (subscriptions.length === 1 && onFirstSubscription) 
           onLastUnsubsribe = onFirstSubscription(set) || null;
      
        // Run the subscription callback, passing in the state.
        run(state);
        
        // return the unsubscribe function 
        return () =>{
          subscriptions = subscriptions.filter(fn => fn !== run);
          
          // No more subscribers, run cleanup to make sure no longer
          // setting state in readonly stores.
          if (subscriptions.length === 0) {
            onLastUnsubsribe && onLastUnsubsribe();
            onLastUnsubsribe = null;
          }
        };
    };

    return {set, subscribe, update}
}

Readable Store

// Create a `Readable` store that only allows reading by subscription.
export const readable = (initialValue, onFirstSubscription) =>  ({
    subscribe: writable(initialValue, onFirstSubscription).subscribe
})

State in readable stores are handled by the callback function onFirstSubscription and will be passed the Set function from the writable store.

// Example of Readable store
const time = readable(new Date(), function onFirstSubscription(set) {

  const interval = setInterval(() => {
    set(new Date());
  }, 1000);

  return function onLastUnsubsribe() {
    clearInterval(interval);
  };
});

Derived Store

// Derive a store from one or more other store values.
const derived = (stores, fn, initial_value) => {
    const single = !Array.isArray(stores);
    const stores_array = single
        ? [stores]
        : stores;

    // If there are 2 arguments in the callback function 
    // then it means `Set` is being used manualy.
    // Otherwise, the value is simply being returned and will need to be `Set`.
    const valueIsReturned = fn.length < 2;

    // Derived is a readable store... Nice!
    return readable(initial_value, set => {
        let inited = false;
        const values = [];
        let cleanup = null;

        const sync = () => {
            // β™» If there is a cleanup this will run on each update before
            // the next function call... This is optional and will only be called 
            // if returned by the function...
            cleanup && cleanup();

            const result = fn(single ? values[0] : values, set);

            if (valueIsReturned) {
                // ⚑ Result is the value, must `Set` it.
                set(result);
            } else {
                // `Set` is manualy used within the function itself, no need to `Set` here.
                // β™» Instead, check to see if a cleanup was returned and if so,
                // β™» capture it for the next call.
                cleanup = typeof result === 'function' ? result : null;
            }
        };

        // πŸͺ Subscribe to each store.
        const unsubscribers = stores_array.map((store, index) => store.subscribe( value => {
            // Store value from store into the array of values.
            // Collect values before getting the computed value of this store.
            values[index] = value;

            // If this has run once already then sync this dirived value from the other stores.
            inited && sync();
          }
        ));
        
        // On first run setup inited and run sync.
        inited = true;
        sync();
        
        // Return a unsubscribe all function, to stop listening to other stores.
        return function stop() {
          unsubscribers.forEach(unsubscribe => unsubscribe());

          // β™» If there is a cleanup function then run it.
          cleanup && cleanup();
        };
    });
}
// Example of derived store using the time store above...
const start = new Date();
const elapsed = derived(
  time,
  $time => Math.round(($time - start) / 1000)
);

// Display the elapsed time.
elapsed.subscribe($time => `The page has been opened ${$time} ${$time === 1 ? 'second' : 'seconds'}`)

// More examples of derived stores.
const a = writable(2)
const b = writable(10)

// Single Subscription
const doubled = derived(a, $a => $a * 2);

// Single Subscription - Asyncronous
const delayedDoubled = derived(a, ($a, set) => {
  setTimeout(() => set($a), 1000);
}, 'one moment...');

// Multiple subscriptions
const summed = derived([a, b], ([$a, $b]) => $a + $b);

// Multiple subscriptions - Asyncronous
const delayedSummed = derived([a, b], ([$a, $b], set) => {
  setTimeout(() => set($a + $b), 1000);
}, 'one moment...');

About

πŸͺ Simple implementation of a Subscription object that notifies when the value changes.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published