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!
example on codepen.
// Create a `Writable` store that allows both updating and reading by subscription.constwritable=(initialState,onFirstSubscription=null)=>{letstate=initialState;letsubscriptions=[];letonLastUnsubsribe=null;// ⚡ Update to new state and notify subscribers.constset=value=>{if(state===value)return;state=value;subscriptions.forEach(fn=>fn(state));};// 🍌 => 🍌 + 🍍 Update state using a callback function and notify all subscribers.constupdate=fn=>{set(fn(state));};// 🎫 Add subscripiton and immediatly run with the current state.constsubscribe=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}}// Create a `Readable` store that only allows reading by subscription.exportconstreadable=(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 storeconsttime=readable(newDate(),functiononFirstSubscription(set){constinterval=setInterval(()=>{set(newDate());},1000);returnfunctiononLastUnsubsribe(){clearInterval(interval);};});// Derive a store from one or more other store values.constderived=(stores,fn,initial_value)=>{constsingle=!Array.isArray(stores);conststores_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`.constvalueIsReturned=fn.length<2;// Derived is a readable store... Nice!returnreadable(initial_value,set=>{letinited=false;constvalues=[];letcleanup=null;constsync=()=>{// ♻ 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();constresult=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=typeofresult==='function' ? result : null;}};// 🏪 Subscribe to each store.constunsubscribers=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.returnfunctionstop(){unsubscribers.forEach(unsubscribe=>unsubscribe());// ♻ If there is a cleanup function then run it.cleanup&&cleanup();};});}// Example of derived store using the time store above...conststart=newDate();constelapsed=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.consta=writable(2)constb=writable(10)// Single Subscriptionconstdoubled=derived(a,$a=>$a*2);// Single Subscription - AsyncronousconstdelayedDoubled=derived(a,($a,set)=>{setTimeout(()=>set($a),1000);},'one moment...');// Multiple subscriptionsconstsummed=derived([a,b],([$a,$b])=>$a+$b);// Multiple subscriptions - AsyncronousconstdelayedSummed=derived([a,b],([$a,$b],set)=>{setTimeout(()=>set($a+$b),1000);},'one moment...');