DEV Community

Cathy Lai
Cathy Lai

Posted on

Choosing the Right Storage Solution

When building a house hunting app in React Native, one of the first architectural decisions you'll face is: Where do I store my data? Should you use SQLite for its query power, AsyncStorage for simplicity, or MMKV for blazing speed?

Let me break down each option so you can make an informed choice.


The Three Contenders

1. AsyncStorage – The Simple Classic

What is it?

AsyncStorage is React Native's built-in key-value storage system. It's asynchronous, simple, and gets the job done for basic data persistence.

Pros:

  • Easy to use – Minimal setup, straightforward API
  • Built into React Native – No extra dependencies (well, needs @react-native-async-storage/async-storage now)
  • Perfect for beginners – Great learning curve
  • Good for small datasets – Settings, user preferences, simple lists

Cons:

  • Slow performance – Asynchronous operations can add latency
  • Size limitations – 6MB limit on Android (configurable but requires native changes)
  • No encryption – Data is stored in plain text
  • No complex queries – Just key-value pairs, no filtering or sorting
  • Async-only – Everything requires await, which can be cumbersome

Best for:

  • Small apps with limited data
  • User settings and preferences
  • Simple lists (< 100 items)
  • Prototypes and MVPs

Code Sample:

import AsyncStorage from '@react-native-async-storage/async-storage';

// Save houses
const saveHouses = async (houses: House[]) => {
  await AsyncStorage.setItem('houses', JSON.stringify(houses));
};

// Load houses
const loadHouses = async () => {
  const data = await AsyncStorage.getItem('houses');
  return data ? JSON.parse(data) : [];
};
Enter fullscreen mode Exit fullscreen mode

2. MMKV – The Performance Champion 🏆

What is it?

MMKV is a lightning-fast key-value storage library developed by WeChat. It uses memory-mapped files and is up to 30x faster than AsyncStorage.

Pros:

  • Extremely fast – Synchronous operations, instant reads/writes
  • Built-in encryption – Add security with one line of code
  • No size limits – Can handle much larger datasets
  • Synchronous API – No await needed, simpler code
  • Type-safe methodsgetString(), getNumber(), getBoolean()
  • Better than AsyncStorage – Drop-in replacement with superior performance
  • Cross-platform – Works on iOS, Android, and even Windows

Cons:

  • Still key-value – No complex queries (like SQLite)
  • Requires installation – Extra dependency (~200KB)
  • Not ideal for relational data – If you need JOIN operations, look elsewhere

Best for:

  • Most React Native apps – This is the sweet spot!
  • House hunting apps with 20-500 listings
  • Apps needing frequent read/write operations
  • Apps requiring data encryption
  • Replacing AsyncStorage for better performance

Simple MMKV Usage

Basic Setup & Usage

// Install first:
// npx expo install react-native-mmkv

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

// 1. Create storage instance (do this once, at the top of your file)
const storage = new MMKV();

// 2. Save data - that's it! No await needed
const houses = [
  { id: '1', title: 'Modern Loft', price: '$2,300' },
  { id: '2', title: 'Cozy Bungalow', price: '$1,850' }
];
storage.set('houses', JSON.stringify(houses));

// 3. Load data - instant!
const savedData = storage.getString('houses');
const myHouses = savedData ? JSON.parse(savedData) : [];

// 4. Delete data
storage.delete('houses');
Enter fullscreen mode Exit fullscreen mode

Simple Context Version (Minimal)

// contexts/HousesContext.tsx
import React, { createContext, useContext, useState, useEffect } from 'react';
import { MMKV } from 'react-native-mmkv';

const storage = new MMKV();
const HousesContext = createContext();

export function HousesProvider({ children }) {
  const [houses, setHouses] = useState([]);

  // Load on start
  useEffect(() => {
    const data = storage.getString('houses');
    if (data) setHouses(JSON.parse(data));
  }, []);

  // Add house
  const addHouse = (house) => {
    const newHouse = { ...house, id: Date.now().toString() };
    const updated = [...houses, newHouse];
    storage.set('houses', JSON.stringify(updated));
    setHouses(updated);
  };

  // Delete house
  const deleteHouse = (id) => {
    const updated = houses.filter(h => h.id !== id);
    storage.set('houses', JSON.stringify(updated));
    setHouses(updated);
  };

  return (
    <HousesContext.Provider value={{ houses, addHouse, deleteHouse }}>
      {children}
    </HousesContext.Provider>
  );
}

export const useHouses = () => useContext(HousesContext);
Enter fullscreen mode Exit fullscreen mode

Using it in Your Components

// In any component
import { useHouses } from '@/contexts/HousesContext';

export default function Houses() {
  const { houses, addHouse, deleteHouse } = useHouses();

  return (
    <FlatList
      data={houses}
      renderItem={({ item }) => (
        <View>
          <Text>{item.title}</Text>
          <Button title="Delete" onPress={() => deleteHouse(item.id)} />
        </View>
      )}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

Key Differences from AsyncStorage

// ❌ AsyncStorage (slower, needs await)
await AsyncStorage.setItem('houses', JSON.stringify(houses));
const data = await AsyncStorage.getItem('houses');

// ✅ MMKV (faster, synchronous)
storage.set('houses', JSON.stringify(houses));
const data = storage.getString('houses');
Enter fullscreen mode Exit fullscreen mode

Advanced MMKV Features

// Multiple storage instances
const userStorage = new MMKV({ id: 'user-data' });
const cacheStorage = new MMKV({ id: 'cache' });

// Encryption
const secureStorage = new MMKV({
  id: 'secure-data',
  encryptionKey: 'my-encryption-key'
});

// Direct type methods (no JSON parsing needed!)
storage.set('viewCount', 42);
storage.set('isLoggedIn', true);

const count = storage.getNumber('viewCount'); // Returns number
const loggedIn = storage.getBoolean('isLoggedIn'); // Returns boolean

// Delete specific key
storage.delete('houses');

// Clear all data
storage.clearAll();

// Get all keys
const keys = storage.getAllKeys();
Enter fullscreen mode Exit fullscreen mode

3. SQLite – The Power User's Choice

What is it?

SQLite is a full-featured relational database that runs locally on the device. It's powerful, battle-tested, and perfect for complex data relationships.

Pros:

  • Complex queries – JOIN, WHERE, ORDER BY, GROUP BY, etc.
  • Relational data – Perfect for normalized data structures
  • Indexing – Fast lookups on specific fields
  • Transactions – ACID compliance for data integrity
  • Large datasets – Can handle thousands of records efficiently
  • SQL power – Full database capabilities

Cons:

  • Complex setup – Requires SQL knowledge
  • More boilerplate – Schema definitions, migrations, queries
  • Overkill for simple apps – Too much complexity for basic CRUD
  • Harder to debug – SQL errors can be cryptic
  • Larger bundle size – Adds ~1-2MB to your app

Best for:

  • Apps with complex data relationships (houses → viewings → notes → documents)
  • Advanced filtering and searching (e.g., "3+ bedrooms, under $2000, within 5 miles")
  • Apps with 1000+ records
  • Data that needs transactions (banking, inventory)
  • Apps requiring offline-first with complex sync logic

Code Sample:

import * as SQLite from 'expo-sqlite';

const db = SQLite.openDatabase('househunt.db');

// Create table
db.transaction(tx => {
  tx.executeSql(
    `CREATE TABLE IF NOT EXISTS houses (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      title TEXT NOT NULL,
      price TEXT,
      location TEXT,
      bedrooms INTEGER,
      created_at DATETIME DEFAULT CURRENT_TIMESTAMP
    );`
  );
});

// Insert house
const addHouse = (house: House) => {
  db.transaction(tx => {
    tx.executeSql(
      'INSERT INTO houses (title, price, location, bedrooms) VALUES (?, ?, ?, ?)',
      [house.title, house.price, house.location, house.bedrooms]
    );
  });
};

// Complex query
const searchHouses = (minBedrooms: number, maxPrice: number) => {
  db.transaction(tx => {
    tx.executeSql(
      'SELECT * FROM houses WHERE bedrooms >= ? AND price <= ? ORDER BY price ASC',
      [minBedrooms, maxPrice],
      (_, { rows }) => console.log(rows._array)
    );
  });
};
Enter fullscreen mode Exit fullscreen mode

Decision Matrix: Which Should You Choose?

Scenario Recommendation Why
Simple house list (< 50 houses) MMKV Fast, simple, encrypts
Medium-size app (50-500 houses) MMKV Best balance of speed/simplicity
Complex filtering ("3BR, < $2k, near work") SQLite Need SQL query power
Just user preferences/settings MMKV or AsyncStorage Either works, MMKV is faster
Large dataset (1000+ houses) SQLite Better performance at scale
Relational data (houses + viewings + notes) SQLite Need JOIN operations
Need encryption MMKV or SQLite Both support encryption
Prototyping quickly MMKV Fastest to implement

Top comments (0)