02examples
Custom persistence adapter
Uncontrolled events saved through your own adapter — sessionStorage with a versioned payload and a per-tenant namespace, plus a tenant switcher.
Two tenants, two calendars. Move something for acme, switch to globex, switch back: the move is still there, and globex never saw it. Reload the tab and both survive; close the tab and both are gone — that is sessionStorage's contract, and exactly what a docs page should leave behind. Clear this tenant wipes one copy and restarts it from its seeds.
liveCustom persistence adapter
loading
uses
How it works
persistence={adapter}hands the uncontrolled store aPersistenceAdapter:loadruns once after mount and returns the stored events ornullfor "nothing stored, keep the seeds";savereceives the full array on every committed change, never mid-drag;clearis what Reset calls. The contract is on the Persistence page.- The payload is versioned, and kloq checks it.
savewrites{ v: PAYLOAD_VERSION, events }— the same envelope the built-inlocalStorageAdapteruses — andloadreads it back throughreadPayload, which validates every event and returnsnullfor a version it does not know, a corrupt copy, or a non-empty payload that validates to nothing. A newer build's data, or a hand-edited key, can never blank the calendar. - Tenancy is two keys.
sessionAdapter("acme")closes overkloq-example:acme:events, andstorage="tenant-acme"gives kloq's own preference keys — theme, scheme, density, zoom — the same namespace (tenant-acme-densityand so on). Both are per-tenant, so a switch is a different calendar in every respect. - Switching remounts.
<Kloq key={tenant}>throws the internal store away and builds a new one from that tenant'sdefaultEvents, thenloadswaps in the stored copy before first paint. The clear button does the same with agenerationcounter after callingadapter.clear(), so the calendar comes back on its seeds instead of saving an empty array. - Why sessionStorage: it is scoped to the tab and evaporates with it, so an
example can persist for real without leaving anything in a visitor's
browser. For an app,
localStorageAdapter("myapp:calendar")is the same adapter over the durable store.
Take it further
- Over the network:
load: () => fetch(url).then((r) => r.json()).then((p) => readPayload(p)?.events ?? null)andsave: (events) => fetch(url, { method: "PUT", body: JSON.stringify({ v: PAYLOAD_VERSION, events }) }).loadmay return a promise; the seeds render until it resolves. - IndexedDB: the same two functions over
get(key)/set(key, payload)from a helper likeidb-keyval. Keep the envelope — the version lives in the data, not the key. - Per change instead of full array: pair the adapter with
onCommitand send eachKloqChangeto your API while the adapter keeps a local copy for instant loads.