AsyncStorage
@react-native-async-storage/async-storage is the standard key-value store on React Native — persistent, async, simple. Use it for user preferences, auth tokens (with care), and offline caches; reach for MMKV or SQLite when you need speed or structure.
API, JSON, security, MMKV, SQLite
EXAMPLE
// 1) Install
// npm install @react-native-async-storage/async-storage
import AsyncStorage from '@react-native-async-storage/async-storage';
// 2) Basic CRUD — all async
await AsyncStorage.setItem('user:name', 'Mara');
const name = await AsyncStorage.getItem('user:name'); // 'Mara' or null
await AsyncStorage.removeItem('user:name');
await AsyncStorage.clear(); // careful — wipes everything
// 3) JSON helpers — wrap so callers don't stringify everywhere
async function setObject(key, value) {
await AsyncStorage.setItem(key, JSON.stringify(value));
}
async function getObject(key) {
const raw = await AsyncStorage.getItem(key);
return raw === null ? null : JSON.parse(raw);
}
await setObject('user', { id: 42, name: 'Mara', email: 'mara@example.com' });
const u = await getObject('user');
// 4) Multi-key operations — atomic batches
await AsyncStorage.multiSet([
['theme', 'dark'],
['lang', 'en-AU'],
]);
const [[, theme], [, lang]] = await AsyncStorage.multiGet(['theme', 'lang']);
await AsyncStorage.multiRemove(['theme', 'lang']);
// 5) List + filter
const keys = await AsyncStorage.getAllKeys();
const userKeys = keys.filter((k) => k.startsWith('user:'));
const pairs = await AsyncStorage.multiGet(userKeys);
// 6) Common patterns
// User preferences hook
import { useEffect, useState } from 'react';
function usePersistentState<T>(key: string, initial: T) {
const [value, setValue] = useState<T>(initial);
useEffect(() => {
(async () => {
const raw = await AsyncStorage.getItem(key);
if (raw !== null) setValue(JSON.parse(raw));
})();
}, [key]);
useEffect(() => {
AsyncStorage.setItem(key, JSON.stringify(value));
}, [key, value]);
return [value, setValue] as const;
}
function Settings() {
const [theme, setTheme] = usePersistentState('theme', 'system');
return (
<Picker selectedValue={theme} onValueChange={setTheme}>
<Picker.Item label="System" value="system" />
<Picker.Item label="Light" value="light" />
<Picker.Item label="Dark" value="dark" />
</Picker>
);
}
// 7) Auth tokens — DON'T use AsyncStorage for sensitive secrets
// AsyncStorage is unencrypted on disk. Use a secure store for tokens:
// • iOS: Keychain
// • Android: EncryptedSharedPreferences
// Libraries:
// expo-secure-store // Expo
// react-native-keychain // bare RN
// @react-native-community/keychain
import * as SecureStore from 'expo-secure-store';
await SecureStore.setItemAsync('jwt', token);
const t = await SecureStore.getItemAsync('jwt');
await SecureStore.deleteItemAsync('jwt');
// 8) Caching — use AsyncStorage with a TTL wrapper
async function setCache(key: string, value: unknown, ttlSec: number) {
await AsyncStorage.setItem(key, JSON.stringify({ v: value, exp: Date.now() + ttlSec * 1000 }));
}
async function getCache(key: string) {
const raw = await AsyncStorage.getItem(key);
if (!raw) return null;
const { v, exp } = JSON.parse(raw);
if (exp < Date.now()) { await AsyncStorage.removeItem(key); return null; }
return v;
}
// 9) Migrations — versioning your stored shape
async function migrate() {
const versionRaw = await AsyncStorage.getItem('schema:version');
const version = versionRaw ? parseInt(versionRaw, 10) : 0;
if (version < 1) { /* v0 → v1 */ }
if (version < 2) { /* v1 → v2 */ }
await AsyncStorage.setItem('schema:version', '2');
}
// 10) Performance — async, not free
// • Each get/set is a JSI/JNI call across the bridge
// • Avoid in hot rendering paths; debounce + batch
// • For very hot keys (read 100×/sec), keep an in-memory mirror
// • Don't store large blobs (> 1 MB); use react-native-fs or SQLite
// 11) MMKV — faster alternative
// npm install react-native-mmkv
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV({ id: 'app-prefs', encryptionKey: 'optional' });
storage.set('user.name', 'Mara'); // SYNC
storage.getString('user.name');
storage.set('count', 5);
storage.getNumber('count');
storage.delete('user.name');
storage.contains('user.name');
storage.getAllKeys();
// MMKV is sync, ~30x faster than AsyncStorage. Optional encryption.
// Recommended for any greenfield React Native app.
// 12) SQLite — for structured data
// op-sqlite, expo-sqlite, react-native-sqlite-storage
// Reach for SQLite when:
// • Queries over many records
// • Relational shape (orders + items + customers)
// • Offline-first data with sync
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabaseSync('app.db');
db.execSync('CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, body TEXT, created_at INTEGER)');
db.runSync('INSERT INTO notes (title, body, created_at) VALUES (?, ?, ?)', ['Hi', 'world', Date.now()]);
const rows = db.getAllSync('SELECT * FROM notes ORDER BY created_at DESC');
// 13) Redux-persist with AsyncStorage
import AsyncStorage from '@react-native-async-storage/async-storage';
import { persistStore, persistReducer } from 'redux-persist';
const persistedReducer = persistReducer({ key: 'root', storage: AsyncStorage, whitelist: ['auth'] }, rootReducer);
// Whitelist what you want persisted; don't store huge slices.
// 14) Privacy + cleanup
// • Logout flow: clear sensitive keys (AsyncStorage.multiRemove)
// • App uninstall: AsyncStorage is removed automatically
// • Inspect on simulator: device files at the app's documents directory
// • iOS App Tracking Transparency: ensure stored identifiers comply with your privacy policy
// 15) Common bugs
// • Storing tokens in plain AsyncStorage → use SecureStore / Keychain
// • JSON.parse on null → exception; check raw === null first
// • Awaiting in render → unnecessary re-render storms; load once in useEffect
// • Forgetting that getItem RESOLVES async → race conditions; use the data only after the promise
// • Storing huge blobs → app slow, GC pressure
// • Concurrent multiSet conflicts → race; serialise or use MMKV
// • Stale state after manual storage edit (dev tools) → unmount + remount
// • Migration not run → app crashes on old keys; gate on schema:version
Why it matters
AsyncStorage is fine for non-sensitive prefs and small caches, but it’s async, slow, and unencrypted. For new projects reach for MMKV (synchronous, much faster), put tokens and secrets in Keychain/Keystore via expo-secure-store or react-native-keychain, and use SQLite when your data is genuinely structured.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import AsyncStorage from '@react-native-async-storage/async-storage';
await AsyncStorage.setItem('token', 't0k3n');
const v = await AsyncStorage.getItem('token');
Try it Yourself »
Exercise
Persist a key/value pair.
await AsyncStorage.
('token', 't0k3n');
camelCase.
Discussion
Loading…