useOfflineFirst
Reactive offline-first data binding powered by Legend-State's sync engine.
Combines remote sync with local persistence and automatic retry. Data is available immediately from local cache, then updated from the remote source. Failed remote operations are persisted locally and retried automatically.
useOfflineFirst
Loading...
Syncing...
Local cache + remote sync with automatic retry.
0
import { import useOfflineFirst
useOfflineFirst } from "@usels/web";import { class ObservablePersistLocalStorage
ObservablePersistLocalStorage } from "@usels/web";
interface interface Settings
Settings { Settings.theme: string
theme: string; Settings.language: string
language: string;}
function function Component(): JSX.Element
Component() { const { const data$: any
data$, const isLoaded$: any
isLoaded$, const isPersistLoaded$: any
isPersistLoaded$, const refetch: any
refetch } = import useOfflineFirst
useOfflineFirst<interface Settings
Settings>({ get: () => any
get: () => any
fetch("/api/settings").any
then((r: any
r) => r: any
r.any
json()), set: ({ value }: { value: any;}) => any
set: ({ value: any
value }) => any
fetch("/api/settings", { method: string
method: "PUT", body: any
body: any
JSON.any
stringify(value: any
value), }), persistKey: string
persistKey: "settings", persistPlugin: typeof ObservablePersistLocalStorage
persistPlugin: class ObservablePersistLocalStorage
ObservablePersistLocalStorage, initial: { theme: string; language: string;}
initial: { theme: string
theme: "light", language: string
language: "en" }, });
return ( <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> {const isPersistLoaded$: any
isPersistLoaded$.any
get() ? ( <> <JSX.IntrinsicElements.span: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>
span>{const isLoaded$: any
isLoaded$.any
get() ? "synced" : "from cache"}</JSX.IntrinsicElements.span: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>
span> <JSX.IntrinsicElements.span: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>
span>{const data$: any
data$.any
theme.any
get()}</JSX.IntrinsicElements.span: DetailedHTMLProps<HTMLAttributes<HTMLSpanElement>, HTMLSpanElement>
span> <JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button DOMAttributes<HTMLButtonElement>.onClick?: MouseEventHandler<HTMLButtonElement> | undefined
onClick={() => const data$: any
data$.any
theme.any
set("dark")}>Dark mode</JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button> <JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button DOMAttributes<HTMLButtonElement>.onClick?: MouseEventHandler<HTMLButtonElement> | undefined
onClick={const refetch: any
refetch}>Sync now</JSX.IntrinsicElements.button: DetailedHTMLProps<ButtonHTMLAttributes<HTMLButtonElement>, HTMLButtonElement>
button> </> ) : ( <JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div>Loading cache...</JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> )} </JSX.IntrinsicElements.div: DetailedHTMLProps<HTMLAttributes<HTMLDivElement>, HTMLDivElement>
div> );}import { createOfflineFirst, observable } from "@usels/web";import { ObservablePersistLocalStorage } from "@usels/web";
interface Settings { theme: string; language: string;}
function Component() { "use scope" const { data$, isLoaded$, isPersistLoaded$, refetch } = createOfflineFirst<Settings>({ get: () => fetch("/api/settings").then((r) => r.json()), set: ({ value }) => fetch("/api/settings", { method: "PUT", body: JSON.stringify(value), }), persistKey: "settings", persistPlugin: ObservablePersistLocalStorage, initial: { theme: "light", language: "en" }, });
return ( <div> {isPersistLoaded$.get() ? ( <> <span>{isLoaded$.get() ? "synced" : "from cache"}</span> <span>{data$.theme.get()}</span> <button onClick={() => data$.theme.set("dark")}>Dark mode</button> <button onClick={refetch}>Sync now</button> </> ) : ( <div>Loading cache...</div> )} </div> );}Custom retry
Section titled “Custom retry”import { useOfflineFirst } from "@usels/web";import { ObservablePersistLocalStorage } from "@usels/web";
function Component() { const { data$, clearPersist } = useOfflineFirst<string[]>({ get: () => fetch("/api/items").then((r) => r.json()), persistKey: "items", persistPlugin: ObservablePersistLocalStorage, initial: [], retry: { infinite: false, backoff: "constant", maxDelay: 5000, }, });}import { createOfflineFirst, observable } from "@usels/web";import { ObservablePersistLocalStorage } from "@usels/web";
function Component() { "use scope" const { data$, clearPersist } = createOfflineFirst<string[]>({ get: () => fetch("/api/items").then((r) => r.json()), persistKey: "items", persistPlugin: ObservablePersistLocalStorage, initial: [], retry: { infinite: false, backoff: "constant", maxDelay: 5000, }, });}Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
options | OfflineFirstOptions<T> | - Offline-first sync configuration. |
OfflineFirstOptions
Section titled “OfflineFirstOptions”Returns
Section titled “Returns”OfflineFirstReturn<T>
| Name | Type | Description |
|---|---|---|
data$ | Observable<T> | The synced observable data. |
isLoaded$ | ReadonlyObservable<boolean> | Whether the remote data has been loaded. |
isFetching$ | ReadonlyObservable<boolean> | Whether a fetch (initial or refetch) is currently in progress. |
isPersistLoaded$ | ReadonlyObservable<boolean> | Whether locally persisted data has been loaded. |
error$ | ReadonlyObservable<Error | undefined> | The most recent sync error, if any. |
lastSync$ | ReadonlyObservable<number | undefined> | Timestamp of the last successful sync. |
refetch | () => void | Trigger a manual re-fetch from remote. |
clearPersist | () => void | Clear all locally persisted data. |