Skip to content

Instantly share code, notes, and snippets.

@IGassmann
Created October 16, 2025 13:19
Show Gist options
  • Select an option

  • Save IGassmann/be20f458119d4229d23e3ac7849a58aa to your computer and use it in GitHub Desktop.

Select an option

Save IGassmann/be20f458119d4229d23e3ac7849a58aa to your computer and use it in GitHub Desktop.
makeStoreApi Example
import { useState, useMemo, Suspense } from "react";
import { unstable_batchedUpdates as batchUpdates } from "react-dom";
import {
StoreRegistry,
StoreRegistryProvider,
useStoreRegistry,
makeStoreApi,
StoreApi,
} from "@livestore/react";
import {
workspaceSchema,
workspaceAdapter,
workspaceEvents,
selectWorkspaceQuery,
selectWorkspaceIssueIdsQuery,
} from "./workspace";
import {
issueSchema,
issueAdapter,
issueEvents,
selectIssueQuery,
} from "./issue";
// --- Provider ---
function App({ children }) {
const [storeRegistry] = useState(
() =>
new StoreRegistry({
defaultOptions: {
batchUpdates,
syncPayload: { authToken: "insecure-token-change-me" },
},
}),
);
return (
<StoreRegistryProvider storeRegistry={storeRegistry}>
{children}
</StoreRegistryProvider>
);
}
// --- Workspace Store ---
export const WorkspaceStore = makeStoreApi({
schema: workspaceSchema,
adapter: workspaceAdapter,
storeId: "workspace-root",
boot: (store) => {
// callback triggered when the store is first loaded
},
gcTime: 3600 * 1000, // 1 hour
});
// --- Issue Store ---
const makeIssueStoreApi = ({ issueId, gcTime = 10 * 1000 }) => {
return makeStoreApi({
schema: issueSchema,
adapter: issueAdapter,
storeId: `issue-${issueId}`,
boot: (issueStore) => {
// Callback triggered when the store is first loaded
},
gcTime,
});
};
export const useIssueStore = ({ issueId, gcTime = 10 * 1000 }) => {
return useMemo(
() => makeIssueStoreApi({ issueId, gcTime }),
[issueId, gcTime],
).use();
};
export const useIssueQuery = (
{ issueId, gcTime }: { issueId: string; gcTime?: number },
...args: Parameters<StoreApi["useQuery"]>
) => {
return useMemo(
() => makeIssueStoreApi({ issueId, gcTime }),
[issueId, gcTime],
).useQuery(...args);
};
export const useIssueClientDocument = (
{ issueId, gcTime }: { issueId: string; gcTime?: number },
...args: Parameters<StoreApi["useClientDocument"]>
) => {
return useMemo(
() => makeIssueStoreApi({ issueId, gcTime }),
[issueId, gcTime],
).useClientDocument(...args);
};
export const preloadIssueStore = (
{ issueId, gcTime }: { issueId: string; gcTime?: number },
...args: Parameters<StoreApi["preload"]>
) => {
return makeIssueStoreApi({ issueId, gcTime }).preload(...args);
};
// --- Usage ---
export async function clientLoader({ context }) {
WorkspaceStore.preload(context.storeRegistry);
}
export default function Workspace() {
const [workspace] = WorkspaceStore.useQuery(selectWorkspaceQuery); // Implicitly loads the store and suspends until it's loaded unless it's already loaded
const issueIds = WorkspaceStore.useQuery(
selectWorkspaceIssueIdsQuery(workspace.id),
); // Implicitly loads the store and suspends until it's loaded unless it's already loaded
const workspaceStore = WorkspaceStore.use(); // We need to separately load the store if we want commit() to be synchronous
const createIssue = () => {
workspaceStore.commit(
workspaceEvents.issueCreated({ title: `Issue ${issueIds.length + 1}` }),
);
};
const [selectedIssueId, setSelectedIssueId] = useState<string>();
const storeRegistry = useStoreRegistry();
const preloadIssue = (issueId: string) =>
preloadIssueStore(
{
issueId,
gcTime: 5 * 1000, // 5s
},
storeRegistry,
);
return (
<>
<p>{workspace.name}</p>
<button onClick={createIssue}>Create Issue</button>
{issueIds.map((id) => (
<button
onClick={() => setSelectedIssueId(id)}
onMouseEnter={() => preloadIssue(id)}
>
Select issue {id}
</button>
))}
{selectedIssueId && (
<Suspense fallback={<>Loading issue…</>}>
<IssuePanel issueId={selectedIssueId} />
</Suspense>
)}
</>
);
}
function IssuePanel({ issueId }: { issueId: string }) {
const [issue] = useIssueQuery(
{ issueId, gcTime: 20 * 1000 }, // Configuration referrent to store loading
selectIssueQuery
); // Implictly loads the store
const issueStore = useIssueStore({
issueId,
gcTime: 10 * 1000, // 20 seconds
}); // We need to separately load the store if we want commit() to be synchronous
const toggleStatus = () => {
issueStore.commit(
issueEvents.issueStatusChanged({
id: issue.id,
status: issue.status === "done" ? "todo" : "done",
}),
);
};
return (
<>
<p>{issue.status}</p>
<button onClick={toggleStatus}>Toggle status</button>
</>
);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment