From harness-claude
Guides on persisting data in React Native/Expo apps using AsyncStorage, SecureStore, MMKV, and SQLite for different use cases.
How this skill is triggered — by the user, by Claude, or both
Slash command
/harness-claude:mobile-storage-patternsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
> Persist data on mobile with AsyncStorage, SecureStore, MMKV, and SQLite for different use cases
Persist data on mobile with AsyncStorage, SecureStore, MMKV, and SQLite for different use cases
| Solution | Best For | Capacity | Speed | Security |
|---|---|---|---|---|
| AsyncStorage | Simple key-value (settings, flags) | ~6MB | Moderate | None |
| expo-secure-store | Tokens, passwords, API keys | ~2KB per item | Moderate | Keychain/Keystore |
| MMKV | High-frequency reads/writes, state persistence | ~unlimited | Very fast | Optional encryption |
| SQLite | Structured relational data, complex queries | ~unlimited | Fast | None (file-level) |
import AsyncStorage from '@react-native-async-storage/async-storage';
// Store
await AsyncStorage.setItem('onboarding_complete', 'true');
await AsyncStorage.setItem('user_preferences', JSON.stringify({ theme: 'dark', locale: 'en' }));
// Retrieve
const isComplete = await AsyncStorage.getItem('onboarding_complete');
const prefs = JSON.parse((await AsyncStorage.getItem('user_preferences')) ?? '{}');
// Remove
await AsyncStorage.removeItem('onboarding_complete');
// Multi operations
await AsyncStorage.multiSet([
['key1', 'value1'],
['key2', 'value2'],
]);
import * as SecureStore from 'expo-secure-store';
// Store securely
await SecureStore.setItemAsync('auth_token', token);
await SecureStore.setItemAsync('refresh_token', refreshToken);
// Retrieve
const token = await SecureStore.getItemAsync('auth_token');
// Delete
await SecureStore.deleteItemAsync('auth_token');
// With options
await SecureStore.setItemAsync('biometric_key', value, {
keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
requireAuthentication: true, // Requires biometric to read
});
npx expo install react-native-mmkv
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();
// Synchronous — no await needed
storage.set('user.id', '12345');
storage.set('user.premium', true);
storage.set('last_sync', Date.now());
const userId = storage.getString('user.id');
const isPremium = storage.getBoolean('user.premium');
storage.delete('user.id');
// With encryption
const secureStorage = new MMKV({
id: 'secure-storage',
encryptionKey: 'your-encryption-key',
});
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();
const mmkvStorage = {
getItem: (name: string) => storage.getString(name) ?? null,
setItem: (name: string, value: string) => storage.set(name, value),
removeItem: (name: string) => storage.delete(name),
};
const useSettingsStore = create(
persist(
(set) => ({
theme: 'light' as 'light' | 'dark',
setTheme: (theme: 'light' | 'dark') => set({ theme }),
}),
{
name: 'settings-storage',
storage: createJSONStorage(() => mmkvStorage),
}
)
);
npx expo install expo-sqlite
import * as SQLite from 'expo-sqlite';
const db = await SQLite.openDatabaseAsync('app.db');
// Create tables
await db.execAsync(`
CREATE TABLE IF NOT EXISTS orders (
id TEXT PRIMARY KEY,
customer_name TEXT NOT NULL,
total REAL NOT NULL,
status TEXT DEFAULT 'pending',
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
`);
// Insert
await db.runAsync('INSERT INTO orders (id, customer_name, total) VALUES (?, ?, ?)', [
orderId,
customerName,
total,
]);
// Query
const orders = await db.getAllAsync<Order>(
'SELECT * FROM orders WHERE status = ? ORDER BY created_at DESC LIMIT ?',
['pending', 20]
);
// Single row
const order = await db.getFirstAsync<Order>('SELECT * FROM orders WHERE id = ?', [orderId]);
class ApiCache {
private storage = new MMKV({ id: 'api-cache' });
async get<T>(key: string, maxAge: number): Promise<T | null> {
const cached = this.storage.getString(key);
if (!cached) return null;
const { data, timestamp } = JSON.parse(cached);
if (Date.now() - timestamp > maxAge) {
this.storage.delete(key);
return null;
}
return data as T;
}
set<T>(key: string, data: T): void {
this.storage.set(key, JSON.stringify({ data, timestamp: Date.now() }));
}
}
async function clearUserData() {
await SecureStore.deleteItemAsync('auth_token');
await SecureStore.deleteItemAsync('refresh_token');
storage.delete('user.id');
storage.delete('user.premium');
// Keep: theme, locale, onboarding_complete
}
AsyncStorage limitations: AsyncStorage is asynchronous, unencrypted, and has platform-specific size limits (~6MB on Android by default). It serializes to JSON, so large datasets are slow. Use it only for small, non-sensitive data.
SecureStore limitations: Individual values are limited to ~2KB. It is async and not suitable for high-frequency reads. Use only for authentication tokens, API keys, and sensitive credentials.
MMKV advantages: Memory-mapped I/O, synchronous API, ~30x faster than AsyncStorage, supports encryption, and has no practical size limit. It is the recommended replacement for AsyncStorage in performance-sensitive apps.
SQLite considerations: Use for data with relationships (users, orders, products), offline-first apps that need complex queries, or datasets too large for key-value storage. Consider using a migration library for schema changes.
Common mistakes:
https://docs.expo.dev/versions/latest/sdk/async-storage/
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeGuides data fetching in React Native/Expo apps using Fetch API and TanStack Query, covering API calls, caching, mutations, authentication tokens, offline support, and request cancellation.
Stores sensitive mobile data using iOS Keychain and Android Keystore instead of plaintext files or SharedPreferences/UserDefaults. Follows OWASP MASVS secure storage requirements.
Provides React Native patterns for Expo workflows, Expo Router navigation, FlatList/MMKV performance optimization, Reanimated animations, Zustand state, and New Architecture (Fabric, TurboModules).