React Native Client

Official SDK v0.5.0 iOS 13+ / Android 8.0+

Getting Started

Installation

npm (Recommended)

npm install @solidb/react-native

# Install peer dependencies
npm install @react-native-async-storage/async-storage
npm install @react-native-community/netinfo

yarn

yarn add @solidb/react-native
yarn add @react-native-async-storage/async-storage
yarn add @react-native-community/netinfo

iOS Setup (Bare Workflow)

After installing dependencies, run:

cd ios && pod install && cd ..

Android Setup (Bare Workflow)

No additional setup required for Android.

Requirements: React Native 0.70+, iOS 13+, Android API 26+ (8.0+). The SDK uses JSI for high-performance native bridge communication.

Expo Setup

SoliDB React Native SDK works with both Expo Go (development) and development builds (production).

Expo Go (Development)

# Install with expo
npx expo install @solidb/react-native
npx expo install @react-native-async-storage/async-storage
npx expo install @react-native-community/netinfo

# Start development server
npx expo start

Development Build (Production)

For optimal performance and full offline sync, create a development build:

# Install expo-dev-client
npx expo install expo-dev-client

# Create native builds
npx expo prebuild
npx expo run:ios
npx expo run:android

# Or use EAS Build
npx eas build --profile development

Expo Workflow Recommendation

Use Expo Go for rapid prototyping. Switch to development builds when you need full offline sync capabilities or release to production.

Bare Workflow Setup

For maximum control and performance, use the bare workflow with direct native module access.

iOS Configuration

// ios/Podfile
platform :ios, '13.0'

target 'YourApp' do
  use_react_native!
  
  # SoliDB pods are auto-linked via CocoaPods
  # Additional permissions for background sync:
  permissions_path = '../node_modules/react-native-permissions/ios'
  pod 'Permission-LocationWhenInUse', :path => "#{permissions_path}/LocationWhenInUse"
end

Android Configuration









  

Quick Start

import React, { useEffect, useState } from 'react';
import { View, Text, Button } from 'react-native';
import { SoliDBClient, SoliDBProvider } from '@solidb/react-native';

// Configure client
const client = new SoliDBClient({
  host: 'api.example.com',
  port: 6745,
  database: 'mydb',
  username: 'admin',
  password: 'password',
  offlineMode: true,        // Enable offline-first
  syncInterval: 30000,      // Auto-sync every 30s
});

// Wrap your app with provider
export default function App() {
  return (
    <SoliDBProvider client={client}>
      <TodoApp />
    </SoliDBProvider>
  );
}

function TodoApp() {
  const [todos, setTodos] = useState([]);

  useEffect(() => {
    // Connect and load initial data
    client.connect().then(async () => {
      const results = await client.query(
        'FOR t IN todos SORT t.created_at DESC RETURN t'
      );
      setTodos(results);
    });
  }, []);

  const addTodo = async (title) => {
    const doc = await client.save('todos', {
      title,
      completed: false,
      created_at: Date.now(),
    });
    setTodos([doc, ...todos]);
  };

  return (
    <View>
      <Text>Todos: {todos.length}</Text>
      {/* Your UI components */}
    </View>
  );
}

Connection Management

// Configure connection
const client = new SoliDBClient({
  host: 'localhost',
  port: 6745,
  database: 'mydb',
  username: 'admin',
  password: 'password',
  timeout: 30000,              // 30 seconds
  enableCompression: true,
  enableSSL: false,            // Set to true for production
});

// Connect with async/await
await client.connect();

// Check connection health
const isHealthy = await client.ping();
console.log('Connection healthy:', isHealthy);

// Disconnect (cleanup in useEffect)
useEffect(() => {
  return () => {
    client.disconnect();
  };
}, []);

// Listen for connection state changes
client.on('connected', () => {
  console.log('Connected to SoliDB');
});

client.on('disconnected', () => {
  console.log('Disconnected from SoliDB');
});

client.on('error', (error) => {
  console.error('Connection error:', error);
});
Property Type Default Description
hoststringlocalhostServer hostname or IP
portnumber6745Server port
timeoutnumber30000Connection timeout (ms)
enableCompressionbooleantrueEnable MessagePack compression
offlineModebooleanfalseEnable offline-first sync
syncIntervalnumber30000Auto-sync interval (ms)

Core Operations

Collection Operations

// List collections in a database
const collections = await client.listCollections();
// => ['users', 'orders', 'products']

// Create a document collection
await client.createCollection('products');

// Create an edge collection (for graphs)
await client.createCollection('relationships', {
  type: 'edge'
});

// Delete a collection
await client.deleteCollection('old_collection');

// Get collection statistics
const stats = await client.collectionStats('users');
console.log('Document count:', stats.document_count);
Method Returns Description
listCollections()string[]List collections in database
createCollection(name, options)Promise<void>Create collection (document or edge)
deleteCollection(name)Promise<void>Delete collection
collectionStats(name)objectGet collection statistics

Document Operations (CRUD)

// SAVE - Create or update a document
const doc = await client.save('users', {
  name: 'Alice',
  email: '[email protected]',
  age: 30,
  tags: ['developer', 'react-native']
});
console.log('Key:', doc._key);  // Auto-generated key

// SAVE with custom key
const docWithKey = await client.save('users', {
  _key: 'user-123',
  name: 'Bob',
  email: '[email protected]'
});

// GET - Retrieve a document by key
const user = await client.get('users', 'user-123');
console.log('Name:', user.name);

// DELETE - Remove a document
await client.delete('users', 'user-123');

// LIST - Paginated document listing
const { docs, total } = await client.list('users', {
  limit: 50,
  offset: 0
});
console.log(`Showing ${docs.length} of ${total} documents`);
Method Returns Description
save(collection, document)Promise<Document>Insert document, returns with key
get(collection, key)Promise<Document>Get document by key
delete(collection, key)Promise<void>Delete document
list(collection, options)Promise<{docs, total}>List documents with pagination

SDBQL Queries

// Simple query
const users = await client.query('FOR u IN users RETURN u');

// Query with bind variables (recommended for security)
const results = await client.query(
  `FOR u IN users
   FILTER u.age >= @min_age AND u.status == @status
   SORT u.created_at DESC
   LIMIT @limit
   RETURN { name: u.name, email: u.email }`,
  {
    min_age: 18,
    status: 'active',
    limit: 100
  }
);

// Aggregation query
const stats = await client.query(
  `FOR u IN users
   COLLECT status = u.status WITH COUNT INTO count
   RETURN { status, count }`
);

// Join query
const orders = await client.query(
  `FOR o IN orders
   FOR u IN users FILTER u._key == o.user_id
   RETURN { order: o, user: u.name }`
);

Live Sync & Subscriptions

// Subscribe to live query updates
const subscription = client.sync.subscribe(
  "FOR u IN users FILTER u.status == 'online' RETURN u",
  {
    onData: (docs) => {
      console.log('Received', docs.length, 'updates');
      // Update your UI
    },
    onError: (error) => {
      console.error('Sync error:', error);
    }
  }
);

// Subscribe to collection changes
const collectionSub = client.sync.subscribeCollection('users', {
  filter: { status: 'active' },
  onInsert: (doc) => {
    console.log('New document:', doc._key);
  },
  onUpdate: (doc) => {
    console.log('Updated:', doc._key);
  },
  onDelete: (key) => {
    console.log('Deleted:', key);
  }
});

// Unsubscribe when done (in useEffect cleanup)
useEffect(() => {
  return () => {
    subscription.unsubscribe();
    collectionSub.unsubscribe();
  };
}, []);

// Manual sync (pull latest changes)
await client.sync.pull();

// Push local changes immediately
await client.sync.push();
Method Description
sync.subscribe(query, callbacks)Subscribe to query results
sync.subscribeCollection(name, callbacks)Subscribe to collection changes
sync.pull()Pull latest changes from server
sync.push()Push local changes to server

React Hooks API

useSync

Manage sync state and control synchronization manually. Perfect for showing sync status in your UI.

import { useSync } from '@solidb/react-native';

function SyncStatus() {
  const { 
    isSyncing,        // boolean - currently syncing
    isConnected,      // boolean - online status
    lastSyncTime,     // Date - last successful sync
    pendingChanges,   // number - unsynced local changes
    error,           // Error | null - last sync error
    sync,            // () => Promise<void> - trigger sync
    push,            // () => Promise<void> - push only
    pull             // () => Promise<void> - pull only
  } = useSync();

  return (
    <View style={styles.container}>
      <Text>
        {isConnected ? '🟢 Online' : '🔴 Offline'}
      </Text>
      
      {isSyncing && (
        <ActivityIndicator size="small" />
      )}
      
      {pendingChanges > 0 && (
        <Text>{pendingChanges} pending changes</Text>
      )}
      
      <Button 
        title="Sync Now" 
        onPress={sync}
        disabled={isSyncing || !isConnected}
      />
      
      {error && (
        <Text style={styles.error}>
          Sync failed: {error.message}
        </Text>
      )}
    </View>
  );
}

useDocument

Reactive hook for individual documents with automatic updates when the document changes.

import { useDocument } from '@solidb/react-native';

function UserProfile({ userId }) {
  const {
    data: user,           // Document | null
    isLoading,           // boolean
    isError,             // boolean
    error,               // Error | null
    update,              // (changes) => Promise<void>
    remove,              // () => Promise<void>
    mutate               // (updater) => void - optimistic update
  } = useDocument('users', userId);

  if (isLoading) return <Text>Loading...</Text>;
  if (isError) return <Text>Error: {error.message}</Text>;
  if (!user) return <Text>User not found</Text>;

  const toggleActive = async () => {
    // Optimistic update - UI updates immediately
    mutate(u => ({ ...u, active: !u.active }));
    
    // Persist change
    await update({ active: !user.active });
  };

  return (
    <View>
      <Text>{user.name}</Text>
      <Text>{user.email}</Text>
      <Switch 
        value={user.active} 
        onValueChange={toggleActive}
      />
      <Button 
        title="Delete" 
        onPress={remove}
        color="red"
      />
    </View>
  );
}

useQuery

Execute SDBQL queries with reactive updates. Automatically re-fetches when dependencies change.

import { useQuery } from '@solidb/react-native';

function UserList({ minAge, status }) {
  const {
    data: users,         // Array | null
    isLoading,
    isError,
    error,
    refetch,             // () => Promise<void> - manual refresh
    isLive               // boolean - real-time updates active
  } = useQuery(
    `FOR u IN users
     FILTER u.age >= @minAge AND u.status == @status
     SORT u.name ASC
     RETURN u`,
    {
      // Bind variables - automatically triggers refetch when changed
      bindVars: { minAge, status },
      
      // Enable live updates via WebSocket
      live: true,
      
      // Custom error handling
      onError: (err) => {
        console.error('Query failed:', err);
      }
    }
  );

  if (isLoading) return <ActivityIndicator />;
  if (isError) return <Text>Error: {error.message}</Text>;

  return (
    <FlatList
      data={users}
      keyExtractor={item => item._key}
      renderItem={({ item }) => (
        <Text>{item.name} ({item.age})</Text>
      )}
      refreshControl={
        <RefreshControl
          refreshing={isLoading}
          onRefresh={refetch}
        />
      }
    />
  );
}

useCollection

High-level hook for collection operations with built-in pagination and real-time updates.

import { useCollection } from '@solidb/react-native';

function TodoList() {
  const {
    data: todos,
    isLoading,
    error,
    
    // Pagination
    hasMore,
    loadMore,
    isLoadingMore,
    
    // CRUD operations
    insert,
    update,
    remove,
    
    // Filtering & sorting
    setFilter,
    setSort,
    
    // Real-time
    isSubscribed,
    subscribe,
    unsubscribe
  } = useCollection('todos', {
    limit: 20,
    sort: 'created_at DESC',
    filter: { completed: false },
    live: true  // Auto-subscribe to changes
  });

  const addTodo = async (title) => {
    await insert({
      title,
      completed: false,
      created_at: Date.now()
    });
  };

  const toggleTodo = async (todo) => {
    await update(todo._key, {
      completed: !todo.completed
    });
  };

  return (
    <View>
      <FlatList
        data={todos}
        renderItem={({ item }) => (
          <TodoItem 
            todo={item} 
            onToggle={() => toggleTodo(item)}
            onDelete={() => remove(item._key)}
          />
        )}
        onEndReached={hasMore ? loadMore : null}
        onEndReachedThreshold={0.5}
        ListFooterComponent={
          isLoadingMore ? <ActivityIndicator /> : null
        }
      />
    </View>
  );
}

Offline-First Sync

Offline-First Architecture

SoliDB React Native provides automatic offline-first capabilities using AsyncStorage. All operations work seamlessly offline and sync when connectivity is restored.

// Enable offline-first mode in configuration
const client = new SoliDBClient({
  host: 'api.example.com',
  port: 6745,
  database: 'mydb',
  username: 'admin',
  password: 'password',
  offlineMode: true,           // Enable offline support
  syncInterval: 30000,         // Auto-sync every 30s
  
  // AsyncStorage configuration
  storage: {
    engine: 'AsyncStorage',    // or 'SQLite' for larger datasets
    encryptionKey: 'optional-key',  // Encrypt local data
  },
  
  // Conflict resolution
  conflictResolution: 'lastWriteWins',  // or 'merge', 'custom'
  
  // Network handling
  retryAttempts: 5,
  retryDelay: 2000,
  syncOnConnectivityChange: true
});

// All operations work offline
// Changes are automatically queued and synced
const doc = await client.save('notes', {
  title: 'Important Note',
  content: '...'
});
// Works even without network! Automatically syncs later.

// Listen for sync events
client.on('sync:start', () => {
  console.log('Sync started');
});

client.on('sync:complete', (result) => {
  console.log(`Synced: ${result.pushed} up, ${result.pulled} down`);
});

client.on('sync:error', (error) => {
  console.error('Sync failed:', error);
});
Instant Operations

All reads and writes happen immediately to AsyncStorage. No network latency.

AsyncStorage

Uses React Native AsyncStorage for offline data persistence. Optional SQLite for large datasets.

Background Sync

Automatic sync with configurable intervals. Syncs when app comes to foreground.

Learn More

For advanced offline sync features like conflict resolution strategies, delta sync, and selective sync, see the Offline Sync Documentation.

Examples

Complete Todo App

A fully functional offline-first Todo app using hooks, real-time sync, and optimistic updates.

import React, { useState, useCallback } from 'react';
import {
  View,
  Text,
  TextInput,
  FlatList,
  TouchableOpacity,
  StyleSheet,
  ActivityIndicator,
} from 'react-native';
import { SoliDBProvider, useCollection, useSync } from '@solidb/react-native';

// Initialize client
const client = new SoliDBClient({
  host: 'api.example.com',
  port: 6745,
  database: 'todoapp',
  username: 'user',
  password: 'pass',
  offlineMode: true,
  syncInterval: 10000,
});

// Main App
export default function App() {
  return (
    <SoliDBProvider client={client}>
      <TodoApp />
    </SoliDBProvider>
  );
}

function TodoApp() {
  const [newTodo, setNewTodo] = useState('');
  
  const {
    data: todos,
    isLoading,
    insert,
    update,
    remove,
    hasMore,
    loadMore,
  } = useCollection('todos', {
    sort: 'created_at DESC',
    limit: 20,
    live: true,
  });

  const { isSyncing, isConnected, pendingChanges, sync } = useSync();

  const addTodo = useCallback(async () => {
    if (!newTodo.trim()) return;
    
    await insert({
      title: newTodo.trim(),
      completed: false,
      created_at: Date.now(),
    });
    
    setNewTodo('');
  }, [newTodo, insert]);

  const toggleTodo = useCallback(async (todo) => {
    await update(todo._key, {
      completed: !todo.completed,
    });
  }, [update]);

  const deleteTodo = useCallback(async (key) => {
    await remove(key);
  }, [remove]);

  if (isLoading) {
    return (
      <View style={styles.center}>
        <ActivityIndicator size="large" />
      </View>
    );
  }

  return (
    <View style={styles.container}>
      {/* Header */}
      <View style={styles.header}>
        <Text style={styles.title}>My Todos</Text>
        <View style={styles.status}>
          <Text style={styles.statusText}>
            {isConnected ? '🟢' : '🔴'}
          </Text>
          {isSyncing && <ActivityIndicator size="small" />}
          {pendingChanges > 0 && (
            <Text style={styles.pending}>
              {pendingChanges}
            </Text>
          )}
          <TouchableOpacity onPress={sync} disabled={isSyncing}>
            <Text style={styles.syncButton}>🔄</Text>
          </TouchableOpacity>
        </View>
      </View>

      {/* Add Todo */}
      <View style={styles.inputContainer}>
        <TextInput
          style={styles.input}
          value={newTodo}
          onChangeText={setNewTodo}
          placeholder="Add new todo..."
          onSubmitEditing={addTodo}
        />
        <TouchableOpacity style={styles.addButton} onPress={addTodo}>
          <Text style={styles.addButtonText}>+</Text>
        </TouchableOpacity>
      </View>

      {/* Todo List */}
      <FlatList
        data={todos}
        keyExtractor={(item) => item._key}
        renderItem={({ item }) => (
          <TodoItem
            todo={item}
            onToggle={() => toggleTodo(item)}
            onDelete={() => deleteTodo(item._key)}
          />
        )}
        onEndReached={hasMore ? loadMore : null}
        onEndReachedThreshold={0.5}
      />
    </View>
  );
}

function TodoItem({ todo, onToggle, onDelete }) {
  return (
    <View style={styles.todoItem}>
      <TouchableOpacity onPress={onToggle} style={styles.checkbox}>
        <Text style={todo.completed ? styles.checked : styles.unchecked}>
          {todo.completed ? '✓' : '○'}
        </Text>
      </TouchableOpacity>
      
      <Text style={[
        styles.todoText,
        todo.completed && styles.todoTextCompleted
      ]}>
        {todo.title}
      </Text>
      
      <TouchableOpacity onPress={onDelete}>
        <Text style={styles.deleteButton}>🗑</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
  },
  center: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  header: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
  },
  status: {
    flexDirection: 'row',
    alignItems: 'center',
    gap: 8,
  },
  statusText: {
    fontSize: 16,
  },
  pending: {
    backgroundColor: '#ff9500',
    color: '#fff',
    paddingHorizontal: 6,
    paddingVertical: 2,
    borderRadius: 10,
    fontSize: 12,
    fontWeight: 'bold',
  },
  syncButton: {
    fontSize: 20,
  },
  inputContainer: {
    flexDirection: 'row',
    padding: 16,
    gap: 8,
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
  },
  addButton: {
    backgroundColor: '#007AFF',
    width: 44,
    height: 44,
    borderRadius: 22,
    justifyContent: 'center',
    alignItems: 'center',
  },
  addButtonText: {
    color: '#fff',
    fontSize: 24,
    fontWeight: 'bold',
  },
  todoItem: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 16,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  checkbox: {
    marginRight: 12,
  },
  checked: {
    fontSize: 20,
    color: '#34C759',
  },
  unchecked: {
    fontSize: 20,
    color: '#C7C7CC',
  },
  todoText: {
    flex: 1,
    fontSize: 16,
  },
  todoTextCompleted: {
    textDecorationLine: 'line-through',
    color: '#999',
  },
  deleteButton: {
    fontSize: 20,
  },
});

TypeScript Support

import { 
  SoliDBClient, 
  SoliDBClientConfig,
  Document,
  useCollection,
  useDocument,
  useQuery 
} from '@solidb/react-native';

// Define your document types
interface Todo extends Document {
  title: string;
  completed: boolean;
  created_at: number;
}

interface User extends Document {
  name: string;
  email: string;
  age: number;
}

// Typed client configuration
const config: SoliDBClientConfig = {
  host: 'api.example.com',
  port: 6745,
  database: 'mydb',
  username: 'admin',
  password: 'password',
  offlineMode: true,
};

const client = new SoliDBClient(config);

// Typed hooks
function TodoComponent() {
  // useCollection with type parameter
  const { data: todos } = useCollection<Todo>('todos', {
    sort: 'created_at DESC',
  });

  // useDocument with type parameter
  const { data: user } = useDocument<User>('users', 'user-123');

  // useQuery with type parameter
  const { data: activeUsers } = useQuery<User[]>(
    'FOR u IN users FILTER u.active == true RETURN u',
    { live: true }
  );

  return null;
}

// Typed client methods
async function typedOperations() {
  const todo = await client.save<Todo>('todos', {
    title: 'Learn TypeScript',
    completed: false,
    created_at: Date.now(),
  });
  
  // todo is fully typed as Todo
  console.log(todo.title);  // TypeScript knows this exists
  
  const user = await client.get<User>('users', 'user-123');
  console.log(user.email);  // Fully typed
}

Error Handling

import { SoliDBError } from '@solidb/react-native';

try {
  await client.connect();
} catch (error) {
  if (error instanceof SoliDBError) {
    switch (error.code) {
      case 'CONNECTION_FAILED':
        console.error('Unable to connect to server:', error.message);
        break;
      case 'AUTHENTICATION_FAILED':
        console.error('Invalid credentials');
        break;
      case 'DOCUMENT_NOT_FOUND':
        console.error('Document not found:', error.key);
        break;
      case 'NETWORK_ERROR':
        console.error('Network error - will retry automatically');
        break;
      case 'SYNC_CONFLICT':
        console.error('Sync conflict detected:', error.message);
        // Handle conflict resolution
        break;
      default:
        console.error('SoliDB error:', error.message);
    }
  } else {
    console.error('Unexpected error:', error);
  }
}

// Error handling in hooks
function UserComponent({ userId }) {
  const { data, isError, error, refetch } = useDocument('users', userId);

  if (isError) {
    return (
      <View style={styles.errorContainer}>
        <Text style={styles.errorText}>
          Failed to load user: {error.message}
        </Text>
        <Button title="Retry" onPress={refetch} />
      </View>
    );
  }

  return null;
}

Platform Setup

iOS Setup

Required configuration for iOS apps using SoliDB React Native SDK.

// ios/YourApp/Info.plist

<!-- Required for network access -->
<key>NSAppTransportSecurity</key>
<dict>
  <key>NSAllowsArbitraryLoads</key>
  <false/>
  <key>NSExceptionDomains</key>
  <dict>
    <key>your-api-server.com</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSExceptionAllowsInsecureHTTPLoads</key>
      <false/>
    </dict>
  </dict>
</dict>

<!-- Background fetch for sync (optional) -->
<key>UIBackgroundModes</key>
<array>
  <string>fetch</string>
</array>

Android Setup

Required configuration for Android apps using SoliDB React Native SDK.

<!-- android/app/src/main/AndroidManifest.xml -->

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<!-- Background sync permissions -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />

<application>
  <!-- SoliDB sync service -->
  <service
    android:name="com.solidb.reactnative.SyncService"
    android:foregroundServiceType="dataSync"
    android:exported="false" />
</application>