Created
October 16, 2025 13:19
-
-
Save IGassmann/be20f458119d4229d23e3ac7849a58aa to your computer and use it in GitHub Desktop.
makeStoreApi Example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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