Local Storage Options for React Native (Shaker)
Local Storage Options for React Native
Section titled “Local Storage Options for React Native”Date: 2026-03-03
Status: Implemented (MMKV)
Context: Domain data for the Shaker mobile app (Expo/React Native) is persisted via a single abstraction in shaker/infra/localStorage.ts.
Current State
Section titled “Current State”- Domain data: Products, venues, tools, glassware, equipment, events, menus, budgets, shopping lists, bar setups, ingredients, and user recipes are persisted using MMKV (
react-native-mmkv) viashaker/infra/localStorage.ts. One-time migration from AsyncStorage runs at startup (shaker/infra/migrateAsyncStorageToMMKV.ts). - In use:
- expo-secure-store – Clerk JWT token cache (
shaker/infra/clerk.tsx) and app preferences (shaker/utils/appPreferences.ts). Use for auth and sensitive data only. - react-native-mmkv – Domain persistence; single instance id
bartendie-domain. Not supported in Expo Go; use a development build (expo run:ios/expo run:androidor EAS dev client). - @react-native-async-storage/async-storage – Kept only for the one-time migration from AsyncStorage to MMKV for existing installs.
- expo-secure-store – Clerk JWT token cache (
- expo-file-system – Present; suitable for file-based storage (e.g. exports, large blobs).
Storage Options (React Native / Expo)
Section titled “Storage Options (React Native / Expo)”1. AsyncStorage
Section titled “1. AsyncStorage”| Aspect | Details |
|---|---|
| What it is | Asynchronous, unencrypted, persistent key-value store. |
| Best for | Small amounts of non-sensitive data: user preferences, app state, cached lists. |
| Pros | Simple API, already in project, well documented, good for JSON. |
| Cons | Slower (JS–native bridge), not ideal for very large datasets, unencrypted. |
| Size | No hard limit like SecureStore; practical limit depends on device. |
| Docs | Async Storage, Expo – Store data |
Example:
import AsyncStorage from '@react-native-async-storage/async-storage';
await AsyncStorage.setItem('inventory_products', JSON.stringify(products));const data = await AsyncStorage.getItem('inventory_products');const products = data != null ? JSON.parse(data) : [];2. Expo SecureStore
Section titled “2. Expo SecureStore”| Aspect | Details |
|---|---|
| What it is | Encrypted key-value store (iOS Keychain / Android Keystore). |
| Best for | Tokens, credentials, any sensitive data. |
| Pros | Encrypted, first-class Expo support, already used for Clerk. |
| Cons | ~2048-byte limit per value; not for large JSON blobs. |
| Use in Bartendie | Keep for auth only (Clerk). Do not use for full inventory/venues. |
3. MMKV (react-native-mmkv)
Section titled “3. MMKV (react-native-mmkv)”| Aspect | Details |
|---|---|
| What it is | Fast key-value store backed by memory-mapped files; optional encryption. |
| Best for | High read/write volume, larger datasets, when AsyncStorage is too slow. |
| Pros | Much faster than AsyncStorage, synchronous API, encryption option, scales better. |
| Cons | Extra native dependency (may need Expo config plugin or dev client), key-value only (no query layer). |
| Expo | react-native-mmkv works with Expo; may require custom dev client / config. |
Use in Bartendie: Domain data (inventory, venues, events, etc.) is stored with MMKV. Expo Go is not supported; use a development build.
4. Expo SQLite (expo-sqlite)
Section titled “4. Expo SQLite (expo-sqlite)”| Aspect | Details |
|---|---|
| What it is | Local SQLite database; persisted across app restarts. |
| Best for | Structured data, relations, querying, sorting, filtering without loading everything into memory. |
| Pros | Real DB (tables, indexes, transactions), good for many products/venues/events, optional encryption (SQLCipher). |
| Cons | More setup (schema, migrations); different mental model than “one key per list”. |
| Install | npx expo install expo-sqlite |
| Docs | Expo SQLite |
Example:
import * as SQLite from 'expo-sqlite';const db = await SQLite.openDatabaseAsync('bartendie.db');await db.execAsync(` CREATE TABLE IF NOT EXISTS products ( id TEXT PRIMARY KEY, name TEXT NOT NULL, data TEXT NOT NULL );`);await db.runAsync('INSERT OR REPLACE INTO products (id, name, data) VALUES (?, ?, ?)', [id, name, JSON.stringify(product)]);5. Expo FileSystem
Section titled “5. Expo FileSystem”| Aspect | Details |
|---|---|
| What it is | Read/write files in app-specific directories. |
| Best for | Exports, imports, large blobs, user files (e.g. export JSON/CSV). |
| Pros | Already in project; full control over file format and size. |
| Cons | No indexing or querying; you implement format, parsing, and concurrency. |
Use in Bartendie: Keep for export/import flows (e.g. shaker/utils/dataExport.ts), not as primary app storage.
Current implementation (MMKV)
Section titled “Current implementation (MMKV)”- Storage abstraction:
shaker/infra/localStorage.tsuses a single MMKV instance (id: 'bartendie-domain'), exposesgetItem<T>(key),setItem(key, value), andSTORAGE_KEYS. All domain data modules use this module only; no direct MMKV or AsyncStorage imports inshaker/data/*. - Hydration: At app startup,
shaker/infra/hydrateLocalStorage.tsruns a one-time migration from AsyncStorage to MMKV (if needed), then reads each key from MMKV and hydrates in-memory data or seeds from mock data. - Expo Go: MMKV requires native code. The app does not support Expo Go for full functionality; use a development build.
- Sensitive data: expo-secure-store remains in use for Clerk and app preferences (
shaker/utils/appPreferences.ts,shaker/utils/userProfile.ts).
Summary table
Section titled “Summary table”| Data type | Storage | Notes |
|---|---|---|
| Auth tokens (Clerk) | expo-secure-store | In use; keep. |
| App preferences (current venue) | expo-secure-store | appPreferences.ts. |
| Domain data (products, venues, tools, events, etc.) | MMKV | Via localStorage.ts; one key per domain. |
| Exports / imports | expo-file-system | Already used for export. |
Switching storage or moving to a backend
Section titled “Switching storage or moving to a backend”Only shaker/infra/localStorage.ts (and the migration module) need to change when replacing MMKV with another store or a GraphQL backend. See Storage to GraphQL backend for how to convert to a proper backend.
References
Section titled “References”- Expo – Store data
- Expo SecureStore
- Expo SQLite
- Async Storage – Usage
- React Native directory – storage
- MOCK_DATA_GUIDE – Current mock data layout in
shaker/data/