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.

Leave a Comment