Loomup Docs
Guide /docs/sdk-react-native

React Native SDK

Package: @loomup/react-native (SDK repository path: packages/react-native).

Thin React Native helpers on top of @loomup/client and @loomup/react: createNativeClient, AsyncStorage-backed session persistence, and re-exported hooks.

Install

bash
# monorepo paths
npm install ../path/to/packages/client ../path/to/packages/react ../path/to/packages/react-native
npm install @react-native-async-storage/async-storage

# or after publish:
# npm install @loomup/client @loomup/react @loomup/react-native @react-native-async-storage/async-storage

Peer dependencies: react ≥ 18, react-native ≥ 0.72, @loomup/client ≥ 0.1.0, @loomup/react ≥ 0.1.0, @react-native-async-storage/async-storage ≥ 1.

Build from the monorepo:

bash
cd packages/client && npm install && npm run build
cd ../react && npm install && npm run build
cd ../react-native && npm install && npm test

Base URL (local dev)

EnvironmentTypical url
Android emulatorhttp://10.0.2.2:3000
iOS simulatorhttp://127.0.0.1:3000
Physical devicehttp://<your-lan-ip>:3000

Production should use HTTPS. Cleartext HTTP may be blocked (Android network security config / iOS ATS) — enable only for local development.

Quick start

tsx
import AsyncStorage from "@react-native-async-storage/async-storage";
import {
  createNativeClient,
  LoomupNativeProvider,
  useAuth,
  useLiveQuery,
  useMutation,
} from "@loomup/react-native";
import { View, Text, Button, FlatList } from "react-native";

// Keep the client stable (module scope).
const client = createNativeClient({ url: "http://10.0.2.2:3000" });

export function App() {
  return (
    <LoomupNativeProvider client={client} asyncStorage={AsyncStorage}>
      <Todos />
    </LoomupNativeProvider>
  );
}

function Todos() {
  const { user, loading: authLoading, signIn, signOut } = useAuth();
  const { data, loading, ready } = useLiveQuery("todos", {
    sort: "-id",
    limit: 50,
    strategy: "merge",
  });
  const { mutate: addTodo } = useMutation((c, title: string) =>
    c.from("todos").insert({ title, completed: 0 }),
  );

  if (authLoading) return <Text>Loading…</Text>;
  if (!user) {
    return (
      <Button
        title="Sign in"
        onPress={() =>
          signIn({ email: "a@b.com", password: "secret12" })
        }
      />
    );
  }

  return (
    <View>
      <Text>{user.email}</Text>
      <Button title="Sign out" onPress={() => signOut()} />
      <Button title="Add" onPress={() => addTodo("Ship RN SDK")} />
      {loading && <Text>Loading todos…</Text>}
      <FlatList
        data={data ?? []}
        keyExtractor={(row) => String(row.id)}
        renderItem={({ item }) => <Text>{String(item.title)}</Text>}
      />
      {ready && <Text>Live</Text>}
    </View>
  );
}

Session persistence defaults to on (persist.enabled defaults to true) and stores access + refresh tokens in AsyncStorage under keys loomup:access / loomup:refresh.

API

createNativeClient(options)

Same as createClient from @loomup/client. React Native provides global fetch and WebSocket. Pass WebSocketImpl only if you need an override.

LoomupNativeProvider

PropDescription
clientExisting LoomupClient (preferred).
optionsCreateClientOptions when client is omitted.
asyncStorageAsyncStorage module (required when persist is enabled and persist.storage is not set).
persist.enabledDefault true (unlike web React SDK).
persist.storageKeyKey prefix (default loomup).
persist.storageCustom TokenStorage (skips AsyncStorage).

Hooks

All hooks from @loomup/react are re-exported: useAuth, useSelect, useRow, useMutation, useSubscribe, useLiveQuery, useLoomup.

Push tokens

Use the underlying client after OS permission + token acquisition (Expo Notifications / FCM). Loomup does not embed those libraries.

ts
import { useLoomup } from "@loomup/react-native";

const client = useLoomup();
await client.push.registerDevice({
  token: expoPushToken,
  provider: "expo",
  platform: "ios",
});

See Push notifications.

asyncStorageAdapter(storage)

Maps AsyncStorage (or any { getItem, setItem, removeItem } store) to TokenStorage for use with LoomupProvider / persist.storage.

SecureStore (optional)

AsyncStorage is not encrypted. For stronger token storage, wrap Expo SecureStore (or similar) as TokenStorage:

ts
import * as SecureStore from "expo-secure-store";
import type { TokenStorage } from "@loomup/react-native";

const secureStorage: TokenStorage = {
  getItem: (key) => SecureStore.getItemAsync(key),
  setItem: (key, value) => SecureStore.setItemAsync(key, value),
  removeItem: (key) => SecureStore.deleteItemAsync(key),
};

// <LoomupNativeProvider
//   client={client}
//   persist={{ enabled: true, storage: secureStorage }}
// />

Realtime & backgrounding

The TypeScript client reconnects with exponential backoff after unexpected WebSocket closes and re-sends subscriptions, then RESYNC catch-up. When the app is backgrounded, the OS may close the socket; on return to foreground the client reconnects automatically if subscriptions are still active. You do not need a separate AppState helper for v1.

Types

Same as the React SDK — generate table maps with:

bash
loomup gen typescript --config loomup.toml --output ./loomup-types.ts
ts
import { createNativeClient } from "@loomup/react-native";
import type { TableMap, TableInsertMap, TableUpdateMap } from "./loomup-types";

const client = createNativeClient<TableMap, TableInsertMap, TableUpdateMap>({
  url: "https://api.example.com",
});

Publish workflow (maintainers)

  1. Publish or link @loomup/client and @loomup/react first (react needs the TokenStorage API).
  2. Bump packages/react-native/package.json version.
  3. npm test / npm run build in packages/react-native.
  4. npm publish --access public.

Offline SQLite cache

sqliteSyncStorage(await SQLite.openDatabaseAsync("loomup-local.db")) adapts Expo SQLite to the canonical SyncStore. See Offline sync v1.

Non-goals (v1)

  • Bundling a specific SQLite native module (the adapter accepts a structural async database).
  • Native modules (JSI / TurboModules).
  • Default SecureStore (use custom TokenStorage).
  • Full Expo example app in the monorepo (docs-only; optional follow-up).