Skip to content
techpotions
React Native · Mobile Development · local storage · AsyncStorage · MMKV · expo-sqlite · encryptionNovember 10, 20267 min read

React Native Async Storage: What to Use Instead

React Native AsyncStorage is the default key-value store, but for many production apps it’s not enough. Here’s when to replace it and how to pick the right alternative—step by step.

Cover illustration for “React Native Async Storage: What to Use Instead”

React Native AsyncStorage is the built‑in key‑value store that ships with every React Native project. It works, but it is a string‑only map with no size guarantees and zero encryption. For settings or a small cache, that’s fine. When your users care about performance, data fidelity, or security, you need something else. In this guide, we’ll move through exactly which option to pick—and how to migrate—with real code you can copy.

When React Native AsyncStorage Still Makes Sense

AsyncStorage is not going away, and it still earns its keep for the simplest cases. If you’re storing:

  • A handful of user preferences (theme, onboarding flag, language selection)
  • Non‑sensitive tokens that you can afford to lose (like a cached list of recently viewed items)
  • Booleans or small strings that never need querying

… then AsyncStorage is a zero‑dependency win. The API is straightforward, it’s async, and it works on every platform. A typical read looks like this:

TypeScript
const value = await AsyncStorage.getItem('@preferences_language');

As soon as your data grows beyond a handful of kilobytes, or you need to search for a specific object inside a set, AsyncStorage becomes a liability.

Why AsyncStorage Isn’t Enough for Production Apps

AsyncStorage is a flat string map. That means:

  • No size promises – the underlying SQLite/Misc nature means there’s no contractual limit, but also no guarantee your data won’t be evicted when the device is under pressure.
  • No encryption – strings are stored in plaintext, accessible to anyone with a rooted device or a backup extraction. For HIPAA, PCI, or even a user token, that’s a non‑starter.
  • String‑only – every value must be JSON.stringify’d and parsed. Complex nested objects are fragile, and type safety is manual.
  • No querying – you can’t ask “give me all items updated in the last week”. You must load the entire key and deserialize, which is slow on large sets.
  • Sync bottlenecks – the async API is fine for a few reads, but under a write‑heavy workload, the queueing can cause UI jank.

Every app eventually grows out of these constraints. The real question: which exit ramp do you take?

MMKV: The Drop‑in Speed Upgrade

If your main pain point is speed and you still want a key‑value model, MMKV is the go‑to replacement. It’s backed by memory‑mapped files from Tencent, so reads are synchronous and writes are nearly instant, even with thousands of keys.

Setup (bare React Native or Expo with dev‑client):

Shell
npm install react-native-mmkv
# For Expo, also run: npx expo install expo-dev-client
TypeScript
import { MMKV } from 'react-native-mmkv';

export const storage = new MMKV({
  id: 'user-global',
  encryptionKey: 'optional-but-recommended-key',
});

Read and write replace AsyncStorage directly:

TypeScript
// AsyncStorage:
await AsyncStorage.setItem('@score', JSON.stringify(100));
const score = JSON.parse(await AsyncStorage.getItem('@score') || '0');

// MMKV (sync read, no parsing):
storage.set('score', 100);
const score = storage.getNumber('score') ?? 0;

MMKV gives you native performance, typed getters (getBoolean, getString, getBuffer), and optional AES encryption. It’s the option we reach for first at techpotions when a client’s settings store becomes a bottleneck and they need to ship immediately.

Adding a native module like MMKV does increase your binary size. Learn how to keep your release lean in our guide on shrinking React Native app size.

Expo SQLite: Relational Storage Without a Backend

The moment you need to ask questions about your data—sort, filter, aggregate—you need a database, not a key‑value store. Expo SQLite gives you a full local SQL engine that runs directly on the device, perfect for offline‑first workflows.

Setup:

Shell
npx expo install expo-sqlite

Create a table and insert with parameterised queries:

TypeScript
import * as SQLite from 'expo-sqlite';

const db = await SQLite.openDatabaseAsync('offline.db');

await db.execAsync(`
  PRAGMA journal_mode = WAL;
  CREATE TABLE IF NOT EXISTS drafts (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    content TEXT NOT NULL,
    updated_at INTEGER DEFAULT (strftime('%s','now'))
  );
`);

await db.runAsync(
  'INSERT INTO drafts (content) VALUES (?)',
  'The meeting follow-up notes...'
);

Query with filters that AsyncStorage could never handle:

TypeScript
const recentDrafts = await db.getAllAsync(
  'SELECT * FROM drafts WHERE updated_at > ? ORDER BY updated_at DESC',
  Date.now() - 7 * 24 * 60 * 60 * 1000
);

expo‑sqlite is the right call when your offline functionality is not just a cache but the primary source of truth that later syncs to a server. It handles gigabytes of structured data without sweating, and the migration story is just a few ALTER TABLE statements.

If syncing logic is the part you dread, techpotions’ mobile services can design a storage layer that merges, resolves conflicts, and never loses a region‑specific draft.

Encryption: Protecting Data That AsyncStorage Leaves Open

AsyncStorage stores everything in plaintext. For credentials, session tokens, or any PII, you need hardware‑backed storage.

  • expo‑secure‑store uses Android Keystore and iOS Keychain, guaranteeing that even a device backup doesn’t leak your secrets. It is a key‑value store limited to small values (usually < 2KB), perfect for access tokens and encryption salts.
  • MMKV with an encryption key gives you a larger, faster, encrypted key‑value store that can hold megabytes of structured data behind AES.

In practice, a common pattern is to put sensitive tokens into SecureStore and everything else into an encrypted MMKV instance. The two complement each other without overlapping.

For teams that need to weigh these options quickly, the React Native storage comparison by PkgPulse is a useful snapshot, though note that it focuses on key‑value stores and doesn’t cover relational requirements.

Migrating from AsyncStorage: A Step‑by‑Step Playbook

Switching storage engines mid‑flight is the part that makes engineers nervous. Here’s a phased approach we use at techpotions that avoids data loss and keeps the app stable through an OTA update.

Phase 1: Dual‑write every change

First, let both AsyncStorage and the new store receive writes. Ship this behind a feature flag.

TypeScript
// migration-store.ts
import AsyncStorage from '@react-native-async-storage/async-storage';
import { MMKV } from 'react-native-mmkv';

const mmkv = new MMKV({ id: 'migration' });

export async function setPreference(key: string, value: string) {
  // AsyncStorage (existing)
  await AsyncStorage.setItem(key, value);
  // MMKV (new)
  mmkv.set(key, value);
}

Phase 2: Back‑fill existing data on app launch

When the user upgrades, migrate all AsyncStorage keys to the new store. This one‑time script runs once inside a splash‑screen gate.

TypeScript
import { MMKV } from 'react-native-mmkv';

const mmkv = new MMKV({ id: 'migration' });

async function fillExistingData() {
  const keys = await AsyncStorage.getAllKeys();
  for (const key of keys) {
    const value = await AsyncStorage.getItem(key);
    if (value !== null) mmkv.set(key, value);
  }
}

Phase 3: Switch reads then drop writes

Once analytics confirm that the new store has all required keys, flip all read operations to the new store. Ship that for a week, then remove the dual‑write and finally the AsyncStorage dependency. The lesson: always keep the migration reversible until you’ve verified real‑world data integrity.

Choosing Your Stack: A Bird’s‑Eye Comparison

Storage

Best For

Data Model

Query Support

Encryption

Write Volume

Migration Complexity

AsyncStorage

Simple preferences, tokens you don’t mind losing

String key‑value

None

None

Low

None (already present)

MMKV

High‑speed key‑value, typed access, settings up to several MB

Key‑value (number, string, bool, buffer)

None built‑in, optional AES key

Per‑instance AES encryption

High (sync)

Low (drop‑in replacement)

expo‑sqlite

Offline‑first, structured data, relational queries

Tables, rows, SQL

Full SQL

Via sqlcipher if self‑compiled, not bundled

Medium‑high

File‑level encryption possible

Medium (schema management)

SecureStore

Access tokens, cryptographic keys, small secrets

Key‑value (<2 KB)

None

Hardware‑backed (Keystore/Keychain)

Very low

Low

When runxbuild evaluated database options for Expo, they noted that AsyncStorage is “fine for small amounts of data”—and that’s still the advice in a nutshell. The moment your data model has any structure, you’re already past its usefulness.

FAQ

Is AsyncStorage deprecated?

No—AsyncStorage is still maintained and bundled with React Native, but it remains a simple, unencrypted string map. For anything beyond basic configuration or small preference sets, you will eventually need something with type safety, queries, or encryption.

Can I use MMKV with Expo?

Yes, MMKV works with Expo when you use a development build or expo-dev-client. It does not run inside Expo Go because it requires native code, but it is a common companion in modern Expo projects that need a fast key‑value store.

What’s the best local storage for an offline‑first chat app?

An offline‑first chat app typically needs real‑time sync and structured querying. expo‑sqlite provides the relational backbone locally, while MMKV can cache small blobs like draft messages. Both can be paired with a server‑side sync engine that techpotions can help design to avoid data loss during reconnect.

Written by
techpotions
All entries
React Native Upgrade Helper: Breaking Changes, Handled
The weekly

One email a week, from the workshop.

What we published, what we shipped, and the free packs as they land. No drip sequence, no webinar, unsubscribe in one click.