React Native Client
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 |
|---|---|---|---|
host | string | localhost | Server hostname or IP |
port | number | 6745 | Server port |
timeout | number | 30000 | Connection timeout (ms) |
enableCompression | boolean | true | Enable MessagePack compression |
offlineMode | boolean | false | Enable offline-first sync |
syncInterval | number | 30000 | Auto-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) | object | Get 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);
});
All reads and writes happen immediately to AsyncStorage. No network latency.
Uses React Native AsyncStorage for offline data persistence. Optional SQLite for large datasets.
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>
Next Steps
Explore more features of the SoliDB React Native SDK: