A reliable offline-first sync vault for persistent network operations in React Native
npm install react-native-sync-vault


A reliable offline-first sync vault for persistent network operations in React Native. Queue API requests offline, persist them in SQLite, and progressively sync when online. Failed requests are never lost.
NEW in v1.2.0+:
- ⨠Automatic API interception - intercepts fetch() and axios() calls automatically
- ā” Event-based reactive updates - hooks use event listeners instead of polling
- š§ Smart caching with intelligent invalidation
- š Resource-aware sync (battery & network quality)
- š Background sync guarantees
- šØ Pre-built UI components
- š Sync metrics & dashboard
- š Basic integrations (React Query, Zustand, Redux) - Note: Basic adapters, manual integration required
š¹ Demo Video: Watch on YouTube
``bashInstall
npm install react-native-sync-vault
`typescript
import { useOfflineQueue } from 'react-native-sync-vault';function MyComponent() {
const queue = useOfflineQueue({
baseUrl: 'https://api.example.com',
interceptor: { enabled: true }, // Automatically intercept fetch()
});
const handleSubmit = async () => {
// Just use fetch() - automatically queued if offline!
const response = await fetch('https://api.example.com/api/users', {
method: 'POST',
body: JSON.stringify({ name: 'John' }),
});
};
}
`What it is NOT
- ā A full-featured database with relationships (like WatermelonDB)
- ā A general-purpose cache (like React Query cache)
- ā An ORM or database abstraction layer
- ā A state management library (like Redux or Zustand)
- ā A polling-based system - uses event-driven architecture
What it IS
- ā
A reliable network operation queue
- ā
Offline-first request persistence
- ā
Progressive sync engine
- ā
Failure persistence and retry logic
- ā
Custom native network monitoring (no external dependencies)
- ā
Automatic API interception - Automatically intercepts
fetch() and axios() calls and queues them when offline
- ā
Event-based reactive updates - Hooks use event listeners for instant UI updates without polling
- ā
Smart caching with intelligent invalidation
- ā
Resource-aware sync (battery & network quality detection)Features
$3
- ā
Offline Request Queue - Queue API requests when offline
- ā
Automatic API Interception - Intercepts fetch() calls automatically
- ā
SQLite Persistence - Requests persisted in native SQLite
- ā
Progressive Sync - Automatic sync when network is restored
- ā
Conflict Resolution - Built-in conflict resolution strategies
- ā
Retry Logic - Automatic retry with exponential backoff
- ā
Native Network Monitoring - No external dependencies$3
- ā
Intelligent Cache - Per-endpoint TTL strategies
- ā
Pattern-Based Invalidation - Automatic cache invalidation on mutations
- ā
Stale-While-Revalidate - Return cached data while fetching fresh
- ā
Multiple Cache Strategies - Cache-first, network-first, stale-while-revalidate$3
- ā
Battery Monitoring - Reduces sync when battery is low
- ā
Network Quality Detection - Defers large uploads on cellular
- ā
Priority-Based Queue - Critical items sync first
- ā
Dynamic Batch Sizing - Adjusts based on available resources$3
- ā
Background Guarantees - Sync survives app kill
- ā
Task Scheduling - Platform-specific background tasks (iOS/Android)
- ā
Retry Policies - Configurable retry with exponential backoff$3
- ā
React Hooks - Easy-to-use React hooks
- ā
UI Components - Pre-built offline status indicators
- ā
Offline-Ready Hooks - React Query-like API (useOfflineQuery, useOfflineMutation)
- ā
Sync Metrics - Track cache hit rates, sync success, and performance
- ā
Debug Tools - Built-in debug screen, logger, and dashboard
- ā
TypeScript - Full TypeScript support$3
- ā ļø React Query - Basic adapter hook (manual integration required)
- ā ļø Zustand - Basic hook to add sync vault to store (manual usage)
- ā
Redux - Middleware for queuing Redux actions with API metadataDocumentation
- š API Reference - Complete API documentation
- š Migration Guide - Migrate from other solutions
- ā” Performance Benchmarks - Performance metrics
- š ļø Error Handling - Error handling guide
- š API Stability - API stability policy
- š Examples - Example applications
Installation
`bash
npm install react-native-sync-vault
or
yarn add react-native-sync-vault
`$3
`bash
cd ios && pod install
`$3
Add network permission to
AndroidManifest.xml:`xml
`Note: The library uses autolinking, so no additional Gradle configuration is needed.
Usage
$3
`typescript
import { useOfflineQueue } from 'react-native-sync-vault';function MyComponent() {
const queue = useOfflineQueue('https://api.example.com');
const handleSubmit = async () => {
try {
const requestId = await queue.push({
method: 'POST',
url: '/api/users',
data: { name: 'John Doe', email: 'john@example.com' },
priority: 1,
});
console.log('Request queued:', requestId);
} catch (error) {
console.error('Failed to queue request:', error);
}
};
return ;
}
`$3
Enable automatic
fetch() and axios() interception - no need to manually queue requests. Queued requests automatically trigger events for reactive UI updates.`typescript
import { useOfflineQueue } from 'react-native-sync-vault';
import axios from 'axios';function MyComponent() {
// Enable automatic interception for both fetch() and axios()
const queue = useOfflineQueue({
baseUrl: 'https://api.example.com',
interceptor: {
enabled: true, // Enable fetch() interception
urlFilter: ['https://api.example.com'], // Optional: only intercept these URLs
axios: {
enabled: true, // Enable axios() interception
// instance: axios, // Optional: use custom axios instance
},
},
});
const handleSubmitWithFetch = async () => {
// Just use fetch() normally - it's automatically intercepted!
try {
const response = await fetch('https://api.example.com/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
name: 'John Doe',
email: 'john@example.com'
}),
});
if (response.status === 202) {
// Request was queued (device is offline)
// Events are automatically emitted: queue:request:added
console.log('Request queued for offline sync');
} else {
// Request executed normally (device is online)
const data = await response.json();
console.log('Request successful:', data);
}
} catch (error) {
console.error('Request failed:', error);
}
};
const handleSubmitWithAxios = async () => {
// Just use axios() normally - it's automatically intercepted!
try {
const response = await axios.post('https://api.example.com/api/users', {
name: 'John Doe',
email: 'john@example.com'
});
if (response.status === 202) {
// Request was queued (device is offline)
// Events are automatically emitted: queue:request:added
console.log('Request queued for offline sync');
} else {
// Request executed normally (device is online)
console.log('Request successful:', response.data);
}
} catch (error: any) {
// Check if it's a queued error
if (error.isQueued && error.status === 202) {
console.log('Request queued for offline sync', error.requestId);
} else {
console.error('Request failed:', error);
}
}
};
return (
);
}
`Note: All hooks (
useQueueStatus, usePendingRequests, useFailedRequests) use event listeners for reactive updates. When requests are queued via interception, events are automatically emitted and your UI updates immediately - no polling required!$3
All status hooks use event-based reactive updates instead of polling. When requests are queued (via
fetch(), axios(), or manual queue.push()), events are automatically emitted and your UI updates immediately.`typescript
import { useQueueStatus, usePendingRequests, useFailedRequests } from 'react-native-sync-vault';
import axios from 'axios';function QueueStatus() {
const { status, loading } = useQueueStatus();
const { pendingRequests } = usePendingRequests();
const { failedRequests, retry } = useFailedRequests();
// Example: When axios request is queued offline, events trigger automatically:
// - queue:request:added event is emitted
// - usePendingRequests hook updates reactively
// - useQueueStatus hook updates reactively
// No polling or manual refresh needed!
const handleAxiosRequest = async () => {
try {
await axios.post('https://api.example.com/api/data', { name: 'Test' });
} catch (error: any) {
if (error.isQueued) {
// Request was queued - UI will update automatically via events
console.log('Request queued, UI will update automatically');
}
}
};
return (
Network: {status?.networkStatus}
Pending: {status?.totalPending}
Failed: {status?.totalFailed}
{/ These lists update automatically when events are emitted /}
{pendingRequests.map((request) => (
{request.method} {request.url}
))}
{failedRequests.map((request) => (
{request.errorMessage}
))}
);
}
`Event-Based Updates: Hooks subscribe to events like
queue:request:added, queue:request:synced, queue:pending:changed, and sync:started for instant, reactive updates without polling.$3
The sync vault uses an event-based architecture for reactive updates. When requests are queued (via
fetch(), axios(), or queue.push()), events are automatically emitted. All hooks subscribe to these events for instant UI updates.Available Events:
-
queue:request:added - Emitted when a request is added to the queue
- queue:request:synced - Emitted when a request is successfully synced
- queue:request:failed - Emitted when a request fails to sync
- queue:status:changed - Emitted when request status changes
- queue:pending:changed - Emitted when pending requests list changes
- queue:failed:changed - Emitted when failed requests list changes
- sync:started - Emitted when sync process starts
- sync:completed - Emitted when sync process completes
- sync:failed - Emitted when sync process fails
- sync:batch:completed - Emitted when a sync batch completesListening to Events Directly:
`typescript
import QueueManager from 'react-native-sync-vault';// Listen to events
QueueManager.on('queue:request:added', ({ requestId, request }) => {
console.log('Request queued:', requestId, request.method, request.url);
});
QueueManager.on('queue:request:synced', ({ requestId }) => {
console.log('Request synced:', requestId);
});
QueueManager.on('sync:started', () => {
console.log('Sync started');
});
QueueManager.on('sync:completed', () => {
console.log('Sync completed');
});
// Clean up listeners
QueueManager.off('queue:request:added', handler);
`Note: Hooks automatically subscribe to these events, so you typically don't need to listen directly unless you're building custom integrations.
$3
Enable intelligent caching with per-endpoint strategies:
`typescript
import { useOfflineQueue } from 'react-native-sync-vault';function MyComponent() {
const queue = useOfflineQueue({
baseUrl: 'https://api.example.com',
cache: {
enabled: true,
defaultTtl: 8 60 60, // 8 hours
strategies: {
'/api/assignments': {
ttl: 24 60 60, // 24h for critical data
staleWhileRevalidate: true
},
},
invalidateOnMutate: {
'POST /api/assignments': ['/api/assignments', '/api/assignments/*'],
}
}
});
// GET requests are automatically cached
const response = await fetch('https://api.example.com/api/assignments');
}
`$3
Optimize sync based on device resources:
`typescript
import { useOfflineQueue } from 'react-native-sync-vault';function MyComponent() {
const queue = useOfflineQueue({
baseUrl: 'https://api.example.com',
resourceAware: {
battery: {
lowThreshold: 20,
action: 'defer_non_critical',
criticalOnly: true
},
network: {
qualityAware: true,
deferLargeUploadsOnCellular: true,
maxPayloadSizeOnCellular: 100 * 1024 // 100KB
}
}
});
}
`$3
Ensure critical operations complete even after app kill:
`typescript
import { useBackgroundSync } from 'react-native-sync-vault';function MyComponent() {
const { guarantee } = useBackgroundSync();
const handleCriticalSubmit = async () => {
await guarantee({
taskId: 'submit-inspection-123',
operation: () => queue.sync(),
requiredNetwork: true,
priority: 'high',
retryPolicy: {
maxAttempts: 3,
backoff: 'exponential'
}
});
};
}
`$3
Use familiar React Query patterns:
`typescript
import { useOfflineQuery, useOfflineMutation } from 'react-native-sync-vault';function MyComponent() {
const { data, isLoading, isStale } = useOfflineQuery('/api/assignments', {
ttl: 8 60 60 * 1000,
staleWhileRevalidate: true
});
const { mutate, isPending } = useOfflineMutation({
fn: (data) => fetch('/api/assignments', {
method: 'POST',
body: JSON.stringify(data)
}),
invalidateOnSuccess: ['/api/assignments']
});
return (
{isLoading && Loading... }
{data && {JSON.stringify(data)} }
);
}
`$3
Add pre-built UI components for offline status:
`typescript
import {
OfflineProvider,
OfflineStatusBar,
QueuedActionsBadge,
SyncProgress
} from 'react-native-sync-vault';function App() {
return (
);
}
`$3
Track sync performance and health:
`typescript
import { useSyncMetrics } from 'react-native-sync-vault';function MetricsDashboard() {
const { metrics } = useSyncMetrics();
return (
Queue Size: {metrics.queueSize}
Cache Hit Rate: {metrics.cacheHitRate}%
Sync Success Rate: {metrics.syncSuccessRate}%
Average Sync Time: {metrics.averageSyncTime}ms
);
}
`$3
ā ļø Important: These integrations are basic adapters with limited functionality. They provide a starting point but require manual integration work for production use. Automatic mutation queuing, cache integration, and error handling are not included.
#### React Query Integration
Status: Basic adapter - returns separate
queryClient and queue objects. Manual integration required.The
useReactQueryWithSyncVault hook returns both a QueryClient and queue instance, but they are not automatically connected. You'll need to manually integrate them:`typescript
import { ReactQueryIntegration } from 'react-native-sync-vault/integrations';
import { useMutation, useQuery } from '@tanstack/react-query';function MyComponent() {
const { queryClient, queue } = ReactQueryIntegration.useReactQueryWithSyncVault({
baseUrl: 'https://api.example.com'
});
// Manual integration example:
// You need to wrap your mutations to use the queue
const mutation = useMutation({
mutationFn: async (data) => {
// Check if offline and queue if needed
if (!queue.networkMonitor?.isOnline()) {
await queue.push({
method: 'POST',
url: '/api/todos',
data
});
return { queued: true };
}
// Otherwise make normal API call
return fetch('https://api.example.com/api/todos', {
method: 'POST',
body: JSON.stringify(data)
}).then(r => r.json());
}
});
// Note: Requires @tanstack/react-query to be installed
// Note: The withSyncVault function is non-functional (only stores config)
}
`Limitations:
- No automatic mutation queuing
- No cache integration between React Query and sync vault
- Manual error handling required
#### Zustand Integration
Status: Basic hook - adds sync vault to store state. Manual usage required.
ā ļø Note: The
withSyncVault function is broken (violates React hooks rules) and should not be used. Only use useZustandWithSyncVault.`typescript
import { create } from 'zustand';
import { ZustandIntegration } from 'react-native-sync-vault/integrations';// Create your Zustand store
const useStore = create((set) => ({
todos: [],
addTodo: (todo) => set((state) => ({ todos: [...state.todos, todo] }))
}));
function MyComponent() {
// Use the integration hook to get store state + syncVault
const store = ZustandIntegration.useZustandWithSyncVault(useStore, {
baseUrl: 'https://api.example.com'
});
// Access your store state plus syncVault
// You'll need to manually use queue.push() for API calls
const handleAddTodo = async (todo) => {
store.addTodo(todo);
// Manually queue API call
await store.syncVault.queue.push({
method: 'POST',
url: '/api/todos',
data: todo
});
};
return (
{store.todos.map(todo => {todo.text} )}
);
}
`Limitations:
- No automatic API call queuing from store actions
- Manual queue management required
- The
withSyncVault wrapper function is broken and should not be used#### Redux Integration
Status: Functional - Basic middleware for queuing actions with API metadata.
This is the most functional integration. It automatically queues Redux actions that include API call metadata:
`typescript
import { createStore, applyMiddleware } from 'redux';
import { ReduxIntegration } from 'react-native-sync-vault/integrations';const middleware = [
ReduxIntegration.createSyncVaultMiddleware({
baseUrl: 'https://api.example.com'
})
];
const store = createStore(reducer, applyMiddleware(...middleware));
// Actions with API call metadata will be queued when offline:
dispatch({
type: 'CREATE_TODO',
payload: { text: 'New todo' },
meta: {
apiCall: {
method: 'POST',
url: '/api/todos',
data: { text: 'New todo' }
},
queueIfOffline: true
}
});
`What it does:
- Automatically queues actions with
meta.apiCall and meta.queueIfOffline: true
- Works when device is offlineLimitations:
- Basic implementation - no automatic sync retry integration
- No automatic success/failure action dispatching
- Requires manual action metadata structure
API Reference
See docs/API_REFERENCE.md for complete API documentation.
$3
Queue Management:
-
useOfflineQueue(config?) - Main hook for queue management
- useQueueStatus() - Monitor queue status (event-based reactive updates)
- usePendingRequests(limit?) - Get pending requests (event-based reactive updates)
- useFailedRequests() - Get failed requests with retry (event-based reactive updates)Caching:
-
useCache(cacheManager?) - Access cache operations
- useSmartCache(config?) - Smart cache with invalidationOffline-Ready:
-
useOfflineQuery(key, options?) - React Query-like query hook
- useOfflineMutation(options?) - React Query-like mutation hookResource & Background:
-
useResourceAwareSync(config?) - Resource-aware sync configuration
- useBackgroundSync() - Background sync guaranteesMetrics:
-
useSyncMetrics() - Sync performance metrics$3
-
queue.push(options) - Queue a request
- queue.getPendingRequests(limit?, offset?) - Get pending requests
- queue.getFailedRequests() - Get failed requests
- queue.sync() - Manually trigger sync
- queue.getCacheManager() - Get cache manager instance$3
-
OfflineProvider - Context provider for offline state
- OfflineStatusBar - Connection status indicator
- QueuedActionsBadge - Pending actions counter
- SyncProgress - Sync progress indicator$3
-
DebugScreen - Built-in debug screen
- QueueLogger - Queue logging utility
- SyncDashboard - Sync metrics dashboard$3
ā ļø Note: All integrations are basic adapters with limited functionality. They require manual integration work for production use.
-
ReactQueryIntegration.useReactQueryWithSyncVault(config) - Returns { queryClient, queue } (manual integration required)
- ZustandIntegration.useZustandWithSyncVault(store, config) - Adds sync vault to Zustand store state (manual usage required)
- ReduxIntegration.createSyncVaultMiddleware(config) - Middleware for queuing Redux actions (functional, basic implementation)
- ReduxIntegration.withSyncVault(reducer, config) - Helper to set up Redux with sync vault middlewareNote:
ZustandIntegration.withSyncVault is broken and should not be used. ReactQueryIntegration.withSyncVault` is non-functional (only stores config).Check out the examples directory for complete example applications:
- Todo App - Complete offline-first todo management with automatic interception, caching, and error handling
- Initialization: < 50ms
- Enqueue time: < 5ms
- Memory overhead: < 1KB per request
- Batch sync: < 100ms per batch of 5 requests
- Cache lookup: < 2ms
- Cache hit rate: 60-80% in typical usage
See Performance Benchmarks for detailed metrics and comparisons.
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
MIT
- š Documentation
- š Report Issues
- š¬ Discussions
- š§ Email: punithm300@gmail.com
---
Made with ā¤ļø for the React Native community