Skip to content

@syncraft-labs/react

@syncraft-labs/react provides the useSync and useSyncSuspense hooks — giving your React components instant writes, IndexedDB persistence, background sync, and offline support.

Built on useSyncExternalStore for tear-free concurrent rendering.

Terminal window
npm install @syncraft-labs/core @syncraft-labs/react

Peer dependencies: React ≥ 18.0.0

Wrap your app with <SyncraftProvider> (required for store registry isolation across requests in SSR):

import { SyncraftProvider, useSync } from "@syncraft-labs/react";
interface TodoState {
todos: Array<{ id: string; text: string; done: boolean }>;
}
function TodoApp() {
const { data, update, isHydrating, isOffline, error } = useSync<TodoState>(
"todos",
{
initialState: { todos: [] },
},
);
if (isHydrating) return <p>Loading from cache…</p>;
return (
<div>
{isOffline && <p>You're offline — changes saved locally</p>}
{error && <p>Error: {error.message}</p>}
<button
onClick={() =>
update((draft) => {
draft.todos.push({
id: crypto.randomUUID(),
text: "New todo",
done: false,
});
})
}
>
Add Todo
</button>
<ul>
{data?.todos.map((t) => (
<li key={t.id}>
<label>
<input
type="checkbox"
checked={t.done}
onChange={() =>
update((draft) => {
const todo = draft.todos.find((x) => x.id === t.id);
if (todo) todo.done = !todo.done;
})
}
/>
{t.text}
</label>
</li>
))}
</ul>
</div>
);
}
export default function App() {
return (
<SyncraftProvider>
<TodoApp />
</SyncraftProvider>
);
}

Context provider that initializes and scopes the Store Registry for your React component tree. Required to prevent cross-request state leakage during SSR.

<SyncraftProvider>
<App />
</SyncraftProvider>

useSync<T>(key, options): UseSyncReturn<T>

Section titled “useSync<T>(key, options): UseSyncReturn<T>”

Standard React hook for subscribing to a SyncStore.

Parameter Type Description
key string Unique IndexedDB storage key
options UseSyncOptions<T> Configuration object
Option Type Default Description
initialState T undefined Default state when IndexedDB is empty
fetcher () => Promise<T> undefined Fetch initial data from remote source
pusher (entries: OutboxEntry<T>[]) => Promise<void> undefined Push pending mutations to server
syncInterval number 5000 Background sync interval (ms)
selector (state: T | undefined) => R undefined Extract specific slice to prevent over-rendering
onSyncStart () => void undefined Fired when background sync starts
onSyncSuccess () => void undefined Fired when background sync succeeds
onSyncError (error: Error) => void undefined Fired when background sync fails
onRollback (error: Error, previousState: T | undefined) => void undefined Fired when local persistence fails and optimistic state reverts
logger SyncraftLogger console Injectable logger replacing hardcoded console calls
maxOutboxSize number 1000 Maximum outbox entries before overflow strategy triggers
overflowStrategy "reject" | "dropOldest" | "forceFlush" "reject" Behavior when maxOutboxSize is reached
onOverflow (info: OutboxOverflowInfo) => void | Promise<void> undefined Callback invoked on outbox overflow events
storageMode "document" | "collection" "document" Storage strategy ("document" or "collection")
idField string undefined Property name of entity ID (required for collection mode)
Property Type Description
data T | undefined Current state (undefined during hydration)
update (updater: DraftUpdater<T>) => void Mutate state with draft (fire-and-forget)
refetch () => Promise<void> Pull fresh data via fetcher
isHydrating boolean true while loading from IndexedDB
isSyncing boolean true while pusher/refetch is running
isOffline boolean true when navigator.onLine is false
error Error | null Last error from set/pusher/refetch
destroyStore () => void Destroy the store for this key

useSyncSuspense<T>(key, options): UseSyncReturn<T>

Section titled “useSyncSuspense<T>(key, options): UseSyncReturn<T>”

React Suspense-compatible version of useSync. Suspends component rendering while the store hydrates from IndexedDB or initial fetcher is executing.

<React.Suspense fallback={<Skeleton />}>
<TodoAppWithSuspense />
</React.Suspense>

Destroy a store and remove it from the specified registry. Closes IndexedDB connection and clears listeners.