Skip to content

Synced-Store UI Subscription Patterns

Table of Contents

React / Preact

typescript
import { useState, useEffect } from "react"; // or "preact/hooks"
import type { AppStoreClient, ItemType } from "../client";

export function App({ store }: { store: AppStoreClient }) {
  const [items, setItems] = useState<ItemType[]>([]);

  useEffect(() => {
    const unsubscribe = store.subscribe(
      (tx) => tx.table("items").entries().toArray(),
      (entries) => {
        setItems(entries.map(([, v]) => v as ItemType).sort((a, b) => a.order - b.order));
      },
    );
    return unsubscribe;
  }, [store]);

  // Mutate — fire-and-forget (optimistic)
  const addItem = () => {
    store.mutate.setItem({ id: crypto.randomUUID(), text: "new" });
  };

  return <ul>{items.map(i => <li key={i.id}>{i.text}</li>)}</ul>;
}

Provider pattern (React): Create a context + hook so components don't receive store as a prop:

typescript
const StoreContext = createContext<AppStoreClient | null>(null);
export const useStore = () => useContext(StoreContext)!;
// Wrap in entry.tsx: <StoreContext.Provider value={store}><App /></StoreContext.Provider>

SolidJS

typescript
import { createStore, reconcile } from "solid-js/store";
import { onMount, onCleanup, For } from "solid-js";

export function App(props: { store: AppStoreClient }) {
  const [state, setState] = createStore<{ items: ItemType[] }>({ items: [] });

  onMount(() => {
    const unsub = props.store.subscribeToTable("items", (entries) => {
      const items = (entries as [{ itemKey: string }, ItemType][])
        .map(([, v]) => v)
        .sort((a, b) => a.order - b.order);
      setState("items", reconcile(items, { key: "id" }));
    });
    onCleanup(() => unsub());
  });

  return <For each={state.items}>{(item) => <div>{item.text}</div>}</For>;
}

Key difference: SolidJS uses subscribeToTable(name, callback) instead of subscribe(query, callback). Use reconcile() with a key to preserve DOM nodes across updates (see docs/solidjs-best-practices.md).

System Tables

For member profiles, membership roster, current-user lookup in the UI, and permission checks, see getting-user-info-of-members.md — covers $userInfo, $users, $permissions, and the getUserInfo helper.

Store Client API

typescript
// Mutations (optimistic, returns confirmation promise)
const { confirmed } = await store.mutate.myMutator(input);
await confirmed; // wait for server acknowledgement

// Queries
const value = await store.query((tx) => tx.table("items").get("key"));

// Subscriptions (React)
const unsub = store.subscribe(
  (tx) => tx.table("items").entries().toArray(),
  (result) => { /* called on every change */ },
);
unsub(); // cleanup

// Subscriptions (SolidJS)
const unsub = store.subscribeToTable("items", (entries, changes) => {
  // entries: full snapshot
  // changes: { added: string[], modified: string[], removed: string[] }
});