KyleHQ

Honest, loyal, senior software engineer, looking to make a real difference by putting people first.

KyleHQ © 2024
The Brilliance of SvelteJS Stores

The Brilliance of SvelteJS Stores

June 13, 2021
|
code
|
feedarmy
|
sveltejs

One of the (in my opinion) most useful components of the Svelte library is that of “stores”. You can read the official documentation here, but in short, a Store is a reactive object in which you “subscribe” to state changes from any component inside your application. Furthermore, stores can be “derived”. Eg a stores value can be based on the value of one or more other store instances.

For me however, things get really interesting with the creation of “custom” stores. The creators of Svelte were/are obviously well aware of the power and flexibility of these stores and have provided a simply interface to adhere to. Basically, any object that implements the “subscribe” method is a store!

Feed Army makes heavy use of the reactive stores as new feed data is pumped into the application via WebSockets. A stores reactive nature means that when feeds are updated, the display logic can easily render new feed contents as data is being consumed.

At the time of writing, Feed Army internally makes use of x4 custom stores

  1. LocalStorageStore
  2. MapStore
  3. ObjectStore
  4. QueueStore

The first x3 stores, (but especially the Map & Object store) all extend native JS types. This is important because you can then utilise the built in and optimised methods that JS provides. For example, a new MapStore instance has an accessor to the underlying Map.Entries method. This makes it easy to use a store just like any other native JS object with the added bonus of course, of being reactive.

What I want to share with you here however are the contents of a naive/simple Queue store. The reason for creating such a store was due to an assumption I had made in regards to “when” Svelte “ticks” and iterating over reactive objects inside the dom/UI. Basically, my display components would “skip” new feed entries which was hardly ideal, hence the creation of a queue. (More about working with Svelte Microtasks here)

First off, here are the example contents of a fairly simple/straight forward Queue class:

export interface QueueInterface {
  count(): number;
  dequeue?(): any;
  enqueue(...argsany): void;
  flush(): any[];
  reset(): void;
  setFifo(fifoboolean): void;
  setLifo(lifoboolean): void;
  truncate(lengthnumber): void;
}

export class queue {   protected elementsany[];   protected fifo = true;   constructor(…argsany) {     this.elements = […args];   }   count() {     return this.elements.length;   }   dequeue?(): any {     if (this.fifo) {       return this.elements.shift();     }     return this.elements.pop();   }   enqueue(…argsany) {     return this.elements.push(…args);   }   // Like dequeue but will flush all queued elements   flush(): any[] {     let elms = [];     while (this.count()) {       elms.push(this.dequeue());     }     return elms;   }   setFifo(fifo = true) {     this.fifo = fifo;   }   setLifo(lifo = true) {     this.fifo = !lifo;   }   reset(): void {     this.truncate(0);   }   truncate(lengthnumber) {     if (Number.isInteger(length&& length > -1) {       this.elements.length = length;     }   } }

export default queue;

No surprises here, so lets see how this class is used via my QueueStore:

import { writableget } from "svelte/store";
import { queue } from "queue";
export interface QueueStore {
  count(): number;
  dequeue?(): any;
  enqueue(...argsany): void;
  flush(): any[];
  reset(): void;
  self(): object;
  setFifo(fifoboolean): void;
  setLifo(lifoboolean): void;
  subscribe(vany): Unsubscriber;
  truncate(lengthnumber): void;
}

export const newQueueStore = function () {   let store = writable(new queue());   return {     count: () => get(store).count(),     dequeue: () => {       let q = get(store);       const item = q.dequeue();       store.set(q);       return item;     },     enqueue: (…argsany=> {       let q = get(store);       q.enqueue(…args);       store.set(q);     },     flush: () => {       let q = get(store);       const e = q.flush();       store.set(q);       return e;     },     reset: () => {       let q = get(store);       q.reset();       store.set(q);     },     self: () => get(store),     setFifo(fifoboolean): void {       let q = get(store);       q.setFifo(fifo);     },     setLifo(lifoboolean): void {       let q = get(store);       q.setLifo(lifo);     },     subscribestore.subscribe,     truncate: (lengthnumber=> {       let q = get(store);       q.truncate(length);       store.set(q);     },   } as QueueStore; };

Notice that I always try to create accessors to the underlying object for consistency. So now with the reactive store, any subscribers will be notified of any queue updates and can “react” accordingly. Subscribers can even get the underlying queue class instance if so desired. For example:

const qs = newQueueStore();
qs.subscribe((q) => {
  while (q.count()) {
    const item = q.dequeue();
    console.log(item); // hello world
  }
  console.log(q === qs.self()); // true
});
qs.enqueue("hello world");

A definite “shout out” to the Svelte creators here. Most flexible and powerful!