> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pylonsync.com/llms.txt
> Use this file to discover all available pages before exploring further.

# React Native SDK

> @pylonsync/react-native — Expo SQLite-backed offline replica + AsyncStorage adapter.

`@pylonsync/react-native` brings the React hooks to mobile. Same API as `@pylonsync/react` plus:

* **AsyncStorage-backed durable replica** — the local sync replica survives app kills (via `AsyncStorageReplicaPersistence`, enabled by default)
* **AsyncStorage bridge** — auth tokens + client id persist correctly on iOS / Android
* **`useNetworkStatus`** hook for offline-aware UI
* **Foreground/background lifecycle handling** — sync pauses when backgrounded, catches up on resume

## Install

```bash theme={null}
bun add @pylonsync/sdk @pylonsync/react @pylonsync/react-native
bun add @react-native-async-storage/async-storage @react-native-community/netinfo
```

`@react-native-async-storage/async-storage` and `@react-native-community/netinfo` are peer dependencies. For Expo projects you're done. For bare React Native, link the native modules per their docs.

## Configure

Call `init()` from `@pylonsync/react-native` once before your first hook. It bootstraps the AsyncStorage bridge (tokens + client id), installs the durable `AsyncStorageReplicaPersistence`, and starts sync:

```typescript theme={null}
import { init } from "@pylonsync/react-native";

// await it before rendering — otherwise the first paint renders against
// an unauthenticated / empty cache.
await init({
  baseUrl: "https://your-app.com",
  appName: "myapp",
});
```

`init()` reads existing pylon keys (`pylon:<app>:client_id`, `pylon:<app>:token`, etc.) into an in-memory cache and writes through to AsyncStorage. The sync engine's storage interface is synchronous — the bridge keeps it that way without making your call sites async. Pass `persist: false` to skip the durable replica, or your own `persistence` adapter.

## Use the React hooks

Same as web — use the `db.*` namespace (it wires the global engine for you) and `useSession(db.sync)`:

```tsx theme={null}
import { db, useSession } from "@pylonsync/react-native";

function TodoScreen() {
  const { auth } = useSession(db.sync);
  const { data: todos } = db.useQuery("Todo");
  const { insert, update, remove } = db.useEntity("Todo");

  return (
    <FlatList
      data={todos}
      keyExtractor={t => t.id}
      renderItem={({ item }) => (
        <TodoRow
          todo={item}
          onToggle={() => update(item.id, { done: !item.done })}
          onDelete={() => remove(item.id)}
        />
      )}
    />
  );
}
```

## Offline persistence

The sync engine ships with `IndexedDBPersistence` for browsers; on RN you swap in the AsyncStorage-backed equivalent. `init()` already installs it by default — wire it manually only when you construct the engine yourself:

```typescript theme={null}
import { createSyncEngine } from "@pylonsync/sync";
import {
  AsyncStorageReplicaPersistence,
  createAsyncStorageBridge,
} from "@pylonsync/react-native";

const storage = await createAsyncStorageBridge();
const persistence = new AsyncStorageReplicaPersistence("myapp");

const engine = createSyncEngine("https://your-app.com", {
  storage,
  persistence,
});
```

Now:

* The replica snapshot + cursor are persisted to AsyncStorage (durable across cold launches)
* The mutation queue is persisted (offline writes survive app kill)
* App startup hydrates from the persisted replica first, then catches up via pull

The RN replica persistence mirrors the crash-safety model of the web client's IndexedDB path — the replica is written before the cursor advances, so a crash mid-pull can't leave the cursor ahead of durable state.

## Network status

```tsx theme={null}
import { useNetworkStatus } from "@pylonsync/react-native";

function Header() {
  const { isOnline, connectionType } = useNetworkStatus();
  if (!isOnline) return <OfflineBanner />;
  if (connectionType === "cellular") return <SlowConnectionBanner />;
  return null;
}
```

The sync engine continues to work offline — mutations queue locally and ship when the network returns. `useNetworkStatus` is for UI affordances.

## Foreground / background

The engine listens for `AppState` changes:

* **Background** → pause WebSocket; mutations still queue locally
* **Foreground** → reconnect, pull missed changes, drain the queue

Subscriptions to multiplayer shards close on background and re-subscribe on foreground (with the `crdt-subscribe` re-send the engine does on every reconnect, so binary CRDT frames keep arriving).

For long-lived background sync (e.g. push-notification-triggered fetch), use the standalone `engine.pull()` from your background task handler.

## Higher-level offline store

For cases where you want a manual cache outside the sync engine (e.g. cache derived data, store user preferences):

```typescript theme={null}
import { OfflineStore } from "@pylonsync/react-native";

const store = new OfflineStore();

await store.saveEntities("FavoriteRecipes", recipes);
const cached = await store.loadEntities("FavoriteRecipes");
```

Backed by AsyncStorage with `pylon:` prefix.

## Performance

* **Use `FlatList`/`SectionList`** with `useQuery` results — they re-render fully on every store change, so list virtualization matters.
* **Memoize derived data** with `useMemo` to avoid re-computing on every store notify.
* **Limit `useQuery` results** — for large entities, use `useInfiniteQuery` instead so the screen doesn't render thousands of rows.
* **Backgrounded sync** uses zero CPU — the engine pauses cleanly.

## Push notifications

Pylon doesn't ship a push provider — use Expo Push, Firebase, OneSignal, or APNs/FCM directly. Pattern:

1. On sign-in, register the device token with your Pylon backend (`POST /api/fn/registerPushToken`).
2. In your function/cron, push when something interesting happens.
3. On notification tap, foreground the app — the sync engine catches up automatically.

## Tested combinations

* Expo SDK 50+
* React Native 0.74+ (peer-dependency floor)
* New Architecture (Fabric / TurboModules) — works
* Hermes JS engine — works (no Loro CRDT WASM dependency in this package)
* Bare React Native — works after manual native module linking

## Differences from web React

| Feature        | Web                | RN                               |
| -------------- | ------------------ | -------------------------------- |
| Storage        | `localStorage`     | AsyncStorage bridge              |
| Persistence    | IndexedDB          | AsyncStorage replica             |
| Transport      | WebSocket          | WebSocket (polyfilled by RN)     |
| Network status | `navigator.onLine` | `useNetworkStatus`               |
| File upload    | `Blob` / `File`    | `FormData` with file URI         |
| Download       | `Blob` URL         | `expo-file-system` to local file |

The hooks and call signatures are identical.
