Learn React Native - AsyncStorage & Local Persistence
Episode 9 of 23

Learn React Native - AsyncStorage & Local Persistence

This episode covers local storage: AsyncStorage for key-value data, SQLite and MMKV for large data, sensitive data encryption, plus offline-first patterns with a queue and sync strategies.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Mobile apps don't always have a connection. Users ride the train, enter an elevator, or walk in a dead zone — and the app still has to be usable. This is where local persistence comes in: storing data on the device so features keep working offline.

Episode 9 covers the local storage options in React Native: AsyncStorage for key-value data, SQLite and MMKV for large data, encryption for sensitive data, then offline-first patterns with a queue and sync strategies. Episode 13 will go deeper into the security side of credential storage.

AsyncStorage for Key-Value Data

Installing and the Basic API

AsyncStorage is a simple asynchronous key-value store, suitable for preferences, drafts, and small caches. Install it from the community:

Install AsyncStorage
npm install @react-native-async-storage/async-storage
JSSave and read AsyncStorage
import AsyncStorage from "@react-native-async-storage/async-storage";
 
const KUNCI = "preferensi_user";
 
async function simpanPreferensi(nilai) {
  await AsyncStorage.setItem(KUNCI, JSON.stringify(nilai));
}
 
async function bacaPreferensi() {
  const mentah = await AsyncStorage.getItem(KUNCI);
  return mentah ? JSON.parse(mentah) : null;
}

Note AsyncStorage.setItem(KUNCI, JSON.stringify(nilai)): AsyncStorage only stores strings, so objects must be serialized with JSON. Never store tokens or passwords here — that's episode 13's business.

When AsyncStorage Is the Right Fit

AsyncStorage is suitable for small to medium data: user preferences, themes, unsent form data, or list caches. Its limitation: all data is loaded into memory at once when read, so it's not ideal for large collections or search.

Large Data: SQLite and MMKV

SQLite for Complex Queries

For structured data with search and relations — chat history, offline lists of thousands of items — SQLite is more appropriate. Use expo-sqlite for Expo projects or a native library for CLI:

Install expo-sqlite
npx expo install expo-sqlite
JSSimple query with SQLite
import { openDatabaseSync } from "expo-sqlite";
 
const db = openDatabaseSync("app.db");
 
db.execSync(
  "CREATE TABLE IF NOT EXISTS catatan (id INTEGER PRIMARY KEY, judul TEXT)"
);
 
db.runSync(
  "INSERT INTO catatan (judul) VALUES (?)",
  "Catatan pertama offline"
);

MMKV for Maximum Speed

MMKV (from WeChat) stores data directly in a file without JSON serialization, with optional encryption — far faster than AsyncStorage. It fits when read-write performance is a priority:

Install MMKV
npm install react-native-mmkv
JSMMKV with encryption
import { MMKV } from "react-native-mmkv";
 
export const storage = new MMKV({
  id: "app-storage",
  encryptionKey: "kunci-dari-server",
});

storage.set("user", data) and storage.getString("user") handle direct storage. Note the encryptionKey — don't store the key inside the bundle, fetch it from secure storage at runtime.

Encrypting Sensitive Data

Separating Ordinary and Sensitive Data

The main rule: public data is fine in ordinary AsyncStorage/MMKV, sensitive data must go in secure storage. For fields that need encryption but you still want MMKV performance, use an MMKV instance with an encryptionKey as in the example above.

Syncing Keys with the Backend

Encryption keys should be derived from the user's identity or fetched from the server over a secure channel, not hardcoded. The common pattern: the user logs in, gets a token, and that token becomes the basis for deriving the local encryption key. Episode 13 covers the right place to store these keys.

Offline-First Patterns

Queue for Pending Actions

The offline-first approach: user actions are written to local storage as a queue, then sent to the server when the connection returns. If sending fails, the action stays in the queue to be retried:

JSOffline action queue
import AsyncStorage from "@react-native-async-storage/async-storage";
 
async function enqueueAksi(aksi) {
  const mentah = await AsyncStorage.getItem("queue_aksi");
  const antrian = mentah ? JSON.parse(mentah) : [];
  antrian.push({ ...aksi, waktu: Date.now() });
  await AsyncStorage.setItem("queue_aksi", JSON.stringify(antrian));
}

The Date.now() above adds a timestamp so the queue can be ordered when processed. Actions wait in the queue until the connection returns, then are sent one by one.

Sync Strategies

When the connection returns, process the queue in time order, remove successful actions, and handle conflicts. Add a connection listener with @react-native-community/netinfo so synchronization happens automatically when the isConnected status changes.

Cache Policy

Server data stored offline is given version and timestamp metadata. When the app is online, compare with the server and update. Caches that are too old should be removed to save storage — episode 16 will discuss data retention further.

Warning

Offline-first doesn't mean storing all data without limits. Set a policy: how long caches stay valid, which actions may be queued, and what happens on version conflicts. Without a policy, storage fills up and synchronization breaks down.

Closing

Episode 9 kept the app useful without a connection: AsyncStorage for key-value, SQLite for complex queries, MMKV for speed, encryption for sensitive data, and offline-first patterns with a queue and automatic synchronization.

Key takeaways:

  • AsyncStorage only stores strings; JSON serialization is required for objects.
  • SQLite for structured data and search; MMKV for fast reads and writes.
  • Sensitive data must be encrypted or stored in secure storage.
  • Encryption keys must not be hardcoded inside the bundle.
  • Offline actions are queued, then sent when the connection returns.
  • Caches need a clear version and expiry policy.

In the next episode, episode 10, we'll discuss native modules and TurboModules: the difference between the old bridge and the New Architecture, the interop layer, writing your first native module in Kotlin and Swift, and calling it from JavaScript.

Learn React Native - AsyncStorage & Local Persistence | Learn React Native