Store
Store is JOPS’s app-wide data store: one shared key-value object with dot-path access. It is deliberately simple. There are no subscriptions and no reactivity: writing to the Store does not update any view. Views read from it when they come to the front, and write to it in event handlers.
The basics
Section titled “The basics”import { Store } from "jops-core";
const store = Store.singleton(); // the single instance, created on first call
store.set("user.name", "Alice"); // creates intermediate objectsstore.get("user.name"); // "Alice"store.get("user"); // { name: "Alice" }store.get("missing.path"); // undefinedstore.set("cart.items", [1, 2, 3]);store.clear(); // resets everything to {}Paths use dots: "user.name" reads name inside user. set() creates the objects along the way.
Reading in onResume()
Section titled “Reading in onResume()”Because the Store is not reactive, read it at the moment a view is shown. onResume() runs on first display and every time the view comes back to the front, so the view always shows current data:
import { View, Store, layout } from "jops-core";
export default class Profile extends View { onResume() { const user = Store.singleton().get("user"); this.get("name").set(layout`${user?.name ?? "Guest"}`); }}Pattern: a small model object
Section titled “Pattern: a small model object”Rather than scattering Store paths across views, put the reads and writes for one kind of data in a small model object. To let visible views update right away, have it announce every change on the EventBus:
import { Store, EventBus } from "jops-core";
export const Tasks = { all: () => Store.singleton().get("tasks") ?? [], save(list) { Store.singleton().set("tasks", list); EventBus.send("tasks:changed"); }, add(title) { Tasks.save([...Tasks.all(), { id: Date.now(), title, done: false }]); }, remove(id) { Tasks.save(Tasks.all().filter((t) => t.id !== id)); },};Views read with Tasks.all() and subscribe to "tasks:changed" in onLayout().
Pattern: caching a list
Section titled “Pattern: caching a list”A list screen can keep what it fetched in the Store, so a detail screen opened from it can show a record without another request:
async onLayout() { const quotes = await fetch("/api/quotes").then((r) => r.json()); Store.singleton().set("quotes", quotes); this.showRows(quotes);}async onResume() { const { id } = Router.singleton().getParams(); const cached = (Store.singleton().get("quotes") ?? []).find((q) => q.id === id); // fall back to the API on a direct link or a reload const quote = cached ?? (await fetch(`/api/quotes/${id}`).then((r) => r.json())); this.showQuote(quote);}Pattern: a stale flag
Section titled “Pattern: a stale flag”When a write happens on another screen, the list can be told to reload without being called directly. The screen that saves sets a flag; the list checks it in onResume():
// after a successful save, in the editorStore.singleton().set("quotesStale", true);// in the list viewasync onResume() { if (Store.singleton().get("quotesStale")) { Store.singleton().set("quotesStale", false); await this.reload(); }}