React Native Local Storage AsyncStorage
Apps need to remember data between sessions — a login token, user preferences, a shopping cart, or offline content. React Native's AsyncStorage stores data directly on the device. The data survives app restarts and stays available without a network connection.
AsyncStorage vs State
State (useState) AsyncStorage ──────────────────────────────────────────────────────── Lives in memory only Written to device storage Clears when app closes Survives app restarts Fast reads/writes Slightly slower (disk I/O) Good for: current screen data Good for: persistent data Example: state → current search text AsyncStorage → saved login token state → items in current form AsyncStorage → user's theme setting
Installing AsyncStorage
npx expo install @react-native-async-storage/async-storage
Core Operations
All AsyncStorage operations are asynchronous — they return Promises. Always use await with them.
Save Data — setItem
import AsyncStorage from '@react-native-async-storage/async-storage';
async function saveUsername(name) {
await AsyncStorage.setItem('username', name);
}
// AsyncStorage stores strings only
// For objects, convert to JSON string first
async function saveUser(user) {
await AsyncStorage.setItem('user', JSON.stringify(user));
}
Read Data — getItem
async function loadUsername() {
const name = await AsyncStorage.getItem('username');
// Returns null if the key doesn't exist
return name;
}
async function loadUser() {
const raw = await AsyncStorage.getItem('user');
if (raw === null) return null;
return JSON.parse(raw); // convert JSON string back to object
}
Delete Data — removeItem
async function clearUsername() {
await AsyncStorage.removeItem('username');
}
Clear All Data
async function clearAll() {
await AsyncStorage.clear();
// Removes every key your app stored
}
Get All Keys
async function listAllKeys() {
const keys = await AsyncStorage.getAllKeys();
console.log(keys); // ['username', 'theme', 'cartItems']
}
Real-World Pattern — Persisting a Theme Setting
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useState, useEffect } from 'react';
import { View, Text, Switch } from 'react-native';
const THEME_KEY = 'app_dark_mode';
export default function SettingsScreen() {
const [darkMode, setDarkMode] = useState(false);
// Load saved setting when screen appears
useEffect(() => {
loadTheme();
}, []);
async function loadTheme() {
const saved = await AsyncStorage.getItem(THEME_KEY);
if (saved !== null) {
setDarkMode(saved === 'true');
}
}
async function toggleTheme(value) {
setDarkMode(value);
await AsyncStorage.setItem(THEME_KEY, String(value));
}
return (
<View style={{ flex: 1, backgroundColor: darkMode ? '#000' : '#fff', padding: 20 }}>
<Text style={{ color: darkMode ? '#fff' : '#000', fontSize: 18 }}>
Dark Mode
</Text>
<Switch value={darkMode} onValueChange={toggleTheme} />
</View>
);
}
User Flow: 1. Open app → loadTheme() reads from AsyncStorage 2. Found 'true' → setDarkMode(true) → dark screen 3. User toggles → toggleTheme(false) 4. State updates → light screen shows immediately 5. AsyncStorage saves 'false' 6. App restarts → loadTheme() reads 'false' → light screen again ✓
Storing Complex Data — A Shopping Cart
const CART_KEY = 'shopping_cart';
async function addToCart(product) {
// Read current cart
const raw = await AsyncStorage.getItem(CART_KEY);
const cart = raw ? JSON.parse(raw) : [];
// Check if item already in cart
const exists = cart.find(item => item.id === product.id);
let updated;
if (exists) {
// Increase quantity
updated = cart.map(item =>
item.id === product.id ? { ...item, qty: item.qty + 1 } : item
);
} else {
// Add new item
updated = [...cart, { ...product, qty: 1 }];
}
await AsyncStorage.setItem(CART_KEY, JSON.stringify(updated));
return updated;
}
async function getCart() {
const raw = await AsyncStorage.getItem(CART_KEY);
return raw ? JSON.parse(raw) : [];
}
async function clearCart() {
await AsyncStorage.removeItem(CART_KEY);
}
Storage view:
Key: 'shopping_cart'
Value: '[{"id":"1","name":"Shirt","price":29.99,"qty":2},
{"id":"2","name":"Shoes","price":79.99,"qty":1}]'
MultiGet and MultiSet — Batch Operations
Reading or writing multiple keys one-by-one is slow. Use multiGet and multiSet to handle several keys in a single operation.
// Write multiple keys at once
await AsyncStorage.multiSet([
['firstName', 'Jane'],
['lastName', 'Doe'],
['email', 'jane@example.com'],
]);
// Read multiple keys at once
const results = await AsyncStorage.multiGet(['firstName', 'lastName', 'email']);
// results = [['firstName', 'Jane'], ['lastName', 'Doe'], ['email', 'jane@example.com']]
const data = Object.fromEntries(results);
// data = { firstName: 'Jane', lastName: 'Doe', email: 'jane@example.com' }
Error Handling
async function safeLoad(key) {
try {
const value = await AsyncStorage.getItem(key);
return value;
} catch (error) {
console.error(`Failed to read ${key}:`, error);
return null; // graceful fallback
}
}
What NOT to Store in AsyncStorage
✓ Store in AsyncStorage: ✗ Do NOT store in AsyncStorage: ─────────────────────────────────────────────────────────────── User preferences (theme, lang) Passwords (use Secure Store) Cached API responses Credit card numbers Shopping cart Private keys or secrets Onboarding completion flag Large binary files (use FileSystem) Last viewed items Sensitive medical data
For sensitive data like passwords or auth tokens, use expo-secure-store instead. It encrypts data using the device's secure hardware.
npx expo install expo-secure-store
import * as SecureStore from 'expo-secure-store';
await SecureStore.setItemAsync('authToken', token);
const token = await SecureStore.getItemAsync('authToken');
Summary
AsyncStorage saves string data on the device that persists across app restarts. Always JSON.stringify objects before saving and JSON.parse them on load. Load saved data in useEffect when the screen mounts. Use multiGet and multiSet for batch operations. Store sensitive credentials in expo-secure-store instead of AsyncStorage.
