<!--
Sitemap:
- [rxfy](/index): Typed, normalized, reactive state — built on RxJS
- [Comparison](/comparison): rxfy versus Redux Toolkit, MobX, Jotai, TanStack Query, and TanStack DB
- [Inspired by](/inspired-by): the libraries and ideas rxfy grew out of
- [Agent Skills](/agent-skills): Accurate rxfy context for AI coding assistants
- [Examples](/examples): Runnable apps, from client-only to fully synced
- [Changelog](/changelog)
- [Getting Started](/getting-started)
- [Create Store](/getting-started/create-store): Normalized reactive state in a client-only app
- [Add SSR](/getting-started/add-ssr): Render the first paint on the server, hydrate with no refetch
- [Add Sync Client](/getting-started/add-sync-client): The full stack: the server publishes, the client syncs
- [Core Concepts](/core-concepts): The ideas rxfy is built on
- [Observables](/core-concepts/observables): A value that changes over time, that you can subscribe to
- [Normalization](/core-concepts/normalization): Store each entity once, reference it by id
- [Late Unwrapping](/core-concepts/late-unwrapping): Unwrap async state at the leaf, not the trunk
- [Server-Side Rendering](/core-concepts/ssr): Dehydrate on the server, hydrate with no refetch
- [rxfy](/rxfy): The core package: atoms, lenses, models, and states
- [createModel](/rxfy/create-model): Typed entities in normalized storage
- [defineState](/rxfy/define-state): Typed, normalized state shapes with fetching and mutations
- [createAtom](/rxfy/create-atom): A reactive cell with synchronous get, set, and modify
- [createLens](/rxfy/create-lens): A two-way view into part of an Atom
- [React Bindings](/react): Hooks and helpers for using rxfy in React
- [useStateData](/react/use-state-data): Fetch, normalize, and subscribe to a query
- [useStatePagedData](/react/use-state-paged-data): Paginated and infinite-scroll lists
- [useModelStore](/react/use-model-store): Subscribe to one normalized entity by id
- [useAtom](/react/use-atom): Two-way binding for any IAtom
- [Pending](/react/pending): Render pending, rejected, and fulfilled UI for any observable
- [usePending](/react/use-pending): The status value behind Pending
- [useObservable](/react/use-observable): Bind a raw Observable to React
- [Sync Client in React](/react/sync-client): StoreProvider, useSyncClient, and update handles
- [rxfy-client](/framework/client): The framework-agnostic browser sync runtime
- [createSyncClient](/framework/client/create-sync-client): Connect a transport and drive the sync loop
- [readSsrGrants](/framework/client/read-ssr-grants): Lift SSR-embedded channel grants
- [rxfy-server](/framework/server): Bind Drizzle tables, write, and publish sync updates
- [defineResource](/framework/server/define-resource): Tie a Drizzle table to an rxfy model
- [createSync](/framework/server/create-server): Wire a storage adapter, hub, and secret into a Live object
- [createInMemoryHub](/framework/server/hub): The socket-keyed pub/sub backbone
- [Writes](/framework/server/writes): sync.create / sync.update / sync.delete and touch
- [Storage adapters](/framework/server/storage-adapters): Persist writes with Drizzle or in memory
- [Sync messages](/framework/server/messages): What travels between server and client
- [Grants](/framework/server/grants): The server signs what it serves; the client subscribes with the token
- [rxfy-ws](/framework/ws): The default WebSocket transport
- [createWsServer](/framework/ws/server): Attach a Hub to WebSocket connections
- [createWsClient](/framework/ws/client): The browser transport with reconnect and replay
- [Custom transports](/framework/ws/custom-transport): Bring your own ClientTransport
- [Guides](/guides): Task-focused walkthroughs of common rxfy patterns
- [Pagination and infinite scroll](/guides/pagination): Load and append pages into one normalized list
-->

# Sync Client in React \[StoreProvider, useSyncClient, and update handles]

The Sync Client itself — [`createSyncClient`](/framework/client) — lives in `rxfy-client` and is
framework-agnostic. This page covers the React side: handing it to `StoreProvider`, reading it with
`useSyncClient`, and the `updatesAvailable$` / `applyUpdates` handles `useStateData` surfaces once a
client is in context.

Build the client from a WebSocket transport, then pass it to `StoreProvider`'s `syncClient` prop
(`rxfy-react` re-exports `createSyncClient`, so you can import it from either package):

```tsx [entry-client.tsx]
import { createModelRegistry } from "rxfy";
import { createSyncClient, StoreProvider } from "rxfy-react";
import { createWsClient } from "rxfy-ws/client";

const registry = createModelRegistry();
const syncClient = createSyncClient({
  registry,
  transport: createWsClient({ url: `${location.protocol === "https:" ? "wss" : "ws"}://${location.host}/live` }),
  renewUrl: "/api/live/renew",
});

hydrateRoot(
  document.getElementById("root")!,
  <StoreProvider registry={registry} ssr syncClient={syncClient}>
    <App />
  </StoreProvider>,
);
```

Once it's in context, `useStateData` uses it automatically: it lifts the `$grant` the server signed
into each fetch (via `sync.serve` / `sync.hydration`), hands it to the client with the entity topics
its data normalized into, and the client subscribes. See [rxfy-client](/framework/client) for the
runtime itself — `createSyncClient`, `readSsrGrants`, reconnection, and grant renewal — and
[Grants](/framework/server/grants) for the grant model.

## `StoreProvider` `syncClient` prop

`syncClient` is optional. When omitted, every `updatesAvailable$` stream emits `0` and `applyUpdates`
falls back to a plain `reload()` — so the same components work with or without the sync stack.

### `useSyncClient`

```ts
import { useSyncClient } from "rxfy-react";

function MyWidget() {
  const sync = useSyncClient(); // SyncClient | null
  // ...
}
```

Returns the `SyncClient` from the nearest `StoreProvider`, or `null` when no `syncClient` prop was
provided. Normally you never call this directly — `useStateData` does it for you — but it is exported
as an escape hatch for custom transports or direct `channel(name)` access.

## `updatesAvailable$` / `applyUpdates`

Both come from the `StateHandle` returned by `useStateData`:

```ts
const { data$, updatesAvailable$, applyUpdates } = useStateData({
  state: postsState,
  fetchFn: fetchPosts,
  params: {},
});
```

`updatesAvailable$` is an `Observable<number>` that starts at `0` and increments each time the server
sends a `stale` message for this state's channel. When no Sync Client is present it stays `0`.

`applyUpdates()` resets the counter to `0` and calls `reload()` — triggering a fresh fetch and updating
`data$` in place.

Render a badge that unwraps `updatesAvailable$` at the leaf with [`Pending`](/react/pending) and calls
`applyUpdates` on click — the [Add Sync Client](/getting-started/add-sync-client) guide builds the full
component. `applyUpdates` also works as the `onCreated` / `onDeleted` callback in mutation forms, so a
local write resets the counter and re-fetches in one step.
