@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.
Install
Section titled “Install”npm install @syncraft-labs/core @syncraft-labs/reactPeer dependencies: React ≥ 18.0.0
Quick Start
Section titled “Quick Start”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> );}API Reference
Section titled “API Reference”SyncraftProvider
Section titled “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 |
UseSyncOptions<T>
Section titled “UseSyncOptions<T>”| 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) |
UseSyncReturn<T>
Section titled “UseSyncReturn<T>”| 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>destroyStore(registry, key): void
Section titled “destroyStore(registry, key): void”Destroy a store and remove it from the specified registry. Closes IndexedDB connection and clears listeners.
