react-brain
Browse decisions
React & language foundations 3App architecture 9UI 12Platform & native 9Build, test, observe, secure 6AI in React apps 3stack composerdecision recordscensusstaleness bencharchitecturechangelogmethodologyroadmap

entriesplatform-native · verified 2026-07-13 · react-native

On-device storage & persistence

reviewedconfidence: mediumthis tier holds 50% on the public scorecard (3/19 graded · 0 overturned) →

related decisions: p2p

cited by: security

re-verified 2× — 2026-07-13 · 2026-07-06 · changelog

recommendation

Small key-value → react-native-mmkv (fast, synchronous). Relational/large → op-sqlite or expo-sqlite. Secrets/tokens → Keychain / secure-store, never AsyncStorage.

  • simple persisted prefs, no perf concern → AsyncStorage is fine
  • frequent synchronous reads → MMKV
  • structured/queryable data → SQLite (op-sqlite for perf)
  • P2P / local-first app → primary persistence is the Hypercore/Autobase/Hyperbee log, not KV/SQLite (KV like AsyncStorage is just for local prefs) — see RB-E-P2P

Options & tradeoffs

the field considered — and why each one isn’t the default here

optiontradeoffevidence
@react-native-async-storage/async-storagesimple async key-value; v3.x is instance-based (createAsyncStorage), getMany/setMany/removeMany, requires RN 0.76+; v2 default export kept for back-compat
react-native-mmkv (v4)fast synchronous key-value; v4 is a full Nitro Modules rewrite (restores Old-Arch compat); common AsyncStorage replacement1.5M/wk · ships in 5/34
op-sqlite / expo-sqliterelational SQLite; op-sqlite is high-perf JSI100k/wk
react-native-keychain / expo-secure-storesecure storage for tokens/secrets (Keychain/Keystore)457k/wk · ships in 8/34

evidence: npm weekly downloads (signals snapshot) · “ships in n/D” = adoption across the production-app census, honest denominators

npm weekly downloads (from the corpus's last signals run): react-native-mmkv 1.5M · @react-native-async-storage/async-storage 4.9M · @op-engineering/op-sqlite 100k · expo-sqlite 545k · react-native-keychain 457k · expo-secure-store 3.4M · @nozbe/watermelondb 56k

Verified notes

AsyncStorage v3.x moved to instance-based storage (createAsyncStorage) and requires React Native 0.76+ (NOT New-Arch-only — that earlier claim was wrong; verified vs the v3 migration docs). react-native-mmkv v4 is a full Nitro rewrite (Oct 2025) that restored Old-Arch compat. Latest: async-storage 3.1.x, mmkv 4.3.x, op-sqlite 17.x (2026-06).

Canonical reading

Editorial annotations on why each piece matters — the articles themselves are the originals; read them there.

SQLite for React Native, but 5x faster and 5x less memoryOscar Franco (op-sqlite author)

The library author walks the JSI/C++ internals that make op-sqlite fast and memory-light — lazy HostObject conversion, std::variant over a custom struct, key-sharing across result rows — with benchmarks. The mechanics of high-performance JSI SQLite, not API usage.

An Interactive Guide to TanStack DBMaxi Ferreira (Frontend at Scale)

Builds the local-first reactive-persistence mental model from first principles — Collections, live queries via differential dataflow, optimistic transactional mutations — contrasting it with request/response fetching. The shift to sync-engine thinking.

Sources

The full explanation

The reviewed long-form essay behind this entry — the why, not a how-to. Also on GitHub.

About on-device storage & persistence

Diataxis: Explanation. This page builds understanding of how to place data on the device — the reasoning behind the picks. It is not an API guide or a migration runbook. Local-first / P2P persistence (Hypercore, Autobase, Hyperbee) is owned by RB-E-P2P; secrets policy depth by RB-E-SECURITY; server-state caching by RB-E-DATA. Read this for the why.

The one principle that organises everything: pick storage by data shape × secrecy

"Where do I store this?" is really three different questions wearing one trenchcoat: small key-value (prefs, flags, last-seen timestamps), relational or large structured data (anything you query), and secrets (tokens, credentials). Each has a different home because each optimizes a different thing — read latency, queryability, and OS-level protection respectively. The classic failure is letting one tool answer all three: AsyncStorage as prefs store, database, and token vault. It's genuinely fine at the first job, wrong for the second, and the recommendation is explicit about the third — never for secrets. Classify by shape and secrecy first; the tool picks itself.

The default, and why

Small key-value → react-native-mmkv (fast, synchronous). Relational/large → op-sqlite or expo-sqlite. Secrets/tokens → Keychain / secure-store, never AsyncStorage.

Three shapes, three homes. MMKV wins small key-value because it's fast and synchronous — no await ceremony for a boolean. SQLite wins structured data because "relational/large" is a query problem, and op-sqlite attacks it with high-perf JSI. Keychain/Keystore wins secrets because secrecy is an OS-protection problem, not a persistence problem — which is why the default ends with "never AsyncStorage" for tokens. AsyncStorage isn't banished: for simple persisted prefs with no perf concern, the when-clause says it's fine.

The landscape, and when each piece earns its place

@react-native-async-storage/async-storage — simple async key-value. v3.x moved to instance-based storage (createAsyncStorage) with getMany/setMany/removeMany, and requires RN 0.76+; the v2 default export is kept for back-compat. Latest is 3.1.x. Earns its place for simple persisted prefs when perf isn't a concern.

react-native-mmkv (v4) — fast synchronous key-value, the common AsyncStorage replacement. v4 is a full Nitro Modules rewrite (Oct 2025) that restored Old-Architecture compatibility. Latest is 4.3.x. Earns its place the moment you have frequent synchronous reads.

op-sqlite / expo-sqlite — relational SQLite for structured/queryable data; op-sqlite is the high-perf JSI option (latest 17.x, 2026-06). The author's own deep-dive explains why it's fast and memory-light: lazy HostObject conversion, std::variant over a custom struct, key-sharing across result rows — with benchmarks.

react-native-keychain / expo-secure-store — secure storage for tokens and secrets, backed by the platform Keychain/Keystore. Earns its place for every credential, unconditionally.

The P2P/local-first log — in a Hypercore/Autobase app, the primary persistence is the Hypercore/Autobase/Hyperbee log itself, not KV or SQLite; KV like AsyncStorage is just for local prefs there. That whole world is RB-E-P2P's.

Tradeoffs and failure modes to name out loud

  • Tokens in AsyncStorage. The one "never" in the default. Secrets need Keychain/Keystore protection; a key-value store is the wrong trust boundary no matter how convenient.
  • AsyncStorage as a database. Structured/queryable data belongs in SQLite. Key-value stores answer "get me X by key", not queries.
  • Misreading the AsyncStorage v3 requirements. v3.x requires RN 0.76+ — but it is not New-Arch-only; that earlier claim was wrong (verified against the v3 migration docs). The v2 default export is kept for back-compat, so the instance-based createAsyncStorage move is a migration, not a cliff.
  • Avoiding MMKV v4 over Old-Arch fears. Backwards: the v4 Nitro rewrite is what restored Old-Arch compat.
  • Giving a local-first app a "main database". In P2P apps the log is the source of truth; making SQLite/KV primary builds a second, divergent one. KV is for prefs only there.
  • Persisting like it's still request/response. The TanStack DB reading marks the shift to sync-engine thinking — Collections, live queries via differential dataflow, optimistic transactional mutations — a different mental model from fetch-then-cache.

How it interacts with the rest of the stack

  • P2P (RB-E-P2P). The when-clause draws the line: local-first apps persist primarily in the Hypercore/Autobase/Hyperbee log; this entry's tools shrink to a prefs sidecar.
  • Security (RB-E-SECURITY). The secrets row is where the two entries meet — Keychain/ Keystore is an OS security boundary, and "never AsyncStorage" for tokens is as much a security rule as a storage one.
  • Data layer (RB-E-DATA). On-device persistence is the other half of the data story: server-state caching lives there, and the TanStack DB reading contrasts local-first reactive persistence with request/response fetching — the boundary both entries share.

In one paragraph

On-device storage is three problems, not one — classify by data shape × secrecy and each picks its own home: small key-value goes to react-native-mmkv (fast, synchronous; v4 is a Nitro rewrite that restored Old-Arch compat), relational/large data goes to op-sqlite or expo-sqlite (op-sqlite for high-perf JSI), and secrets go to Keychain / secure-store — never AsyncStorage. AsyncStorage itself remains fine for simple persisted prefs (v3.x is instance-based and needs RN 0.76+, with the v2 export kept for back-compat), and in P2P local-first apps the whole question shifts: the Hypercore/Autobase/Hyperbee log is the primary persistence, and everything on this page is just the prefs drawer.


See also: RB-E-P2P (the log as primary persistence in local-first apps), RB-E-SECURITY (secrets belong to the OS boundary), RB-E-DATA (server-state caching; sync-engine thinking vs request/response).

Related in platform-native: native · media · native-ui · keyboard · payments · brownfield · games · alt-frameworks