Skip to main content

Implementing Data Persistence

1. Using AsyncStorage API

Example: Basic AsyncStorage Usage

import { AsyncStorage } from 'react-native';

// Save data
await AsyncStorage.setItem('userToken', 'abc123');

// Retrieve data
const token = await AsyncStorage.getItem('userToken');
console.log(token); // "abc123"
Method Description Notes
setItem(key, value) Saves a key-value pair Value must be a string
getItem(key) Retrieves a value by key Returns a promise resolving to the value
removeItem(key) Deletes a key-value pair No return value
getAllKeys() Retrieves all keys Returns a promise with an array of keys
multiSet(keyValuePairs) Sets multiple key-value pairs Takes an array of [key, value] tuples
multiGet(keys) Retrieves multiple values Returns a promise with an array of [key, value] tuples
Note: AsyncStorage is built on top of the underlying platform's storage (e.g., SQLite on Android, NSUserDefaults on iOS). It is not suitable for large data or sensitive information.

2. Storing Complex Data

Example: Saving a User Profile Object

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

const saveUserProfile = async (userProfile) => {
  try {
    const jsonValue = JSON.stringify(userProfile);
    await AsyncStorage.setItem('userProfile', jsonValue);
  } catch (e) {
    console.error('Failed to save user profile', e);
  }
};

// Usage
const userProfile = {
  id: '12345',
  name: 'John Doe',
  preferences: {
    theme: 'dark',
    notifications: true
  }
};

saveUserProfile(userProfile);
Method Description Notes
AsyncStorage.setItem(key, value) Saves a key-value pair Value must be a string
AsyncStorage.getItem(key) Retrieves a value by key Returns a promise resolving to the string value
AsyncStorage.multiSet(pairs) Sets multiple key-value pairs Accepts array of [key, value] tuples
AsyncStorage.multiGet(keys) Retrieves multiple values by keys Returns array of [key, value] tuples
Note: For complex data structures, always serialize with JSON before storing and deserialize when retrieving.

Example: Loading and Parsing User Preferences

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

const loadUserPreferences = async () => {
  try {
    const jsonValue = await AsyncStorage.getItem('userPreferences');
    if (jsonValue != null) {
      const preferences = JSON.parse(jsonValue);
      return preferences;
    }
  } catch (e) {
    console.error('Failed to load user preferences', e);
  }
  return null;
};

// Usage
const prefs = await loadUserPreferences();
console.log(prefs?.theme); // "dark"

Best Practices for Complex Data Storage

  • Use JSON.stringify() and JSON.parse() for complex objects
  • Wrap storage operations in try-catch blocks
  • Consider using a wrapper function to abstract AsyncStorage usage
  • Be mindful of storage limits (typically 6MB on mobile)

3. Implementing Data Encryption

Feature Description Implementation
Encryption Algorithm Used for securing data at rest AES-256-GCM
Key Management Secure storage and retrieval of encryption keys Hardware-backed keystore (Android) or Keychain (iOS)
Data Format Encrypted data structure {iv: string, data: string}

Example: Encrypting Data with AES-256-GCM

import { encrypt, decrypt } from 'react-native-crypto'; // hypothetical library

const secretKey = 'your-256-bit-secret-key-here';
const dataToEncrypt = JSON.stringify({ userId: '123', token: 'abc123' });

const encrypted = encrypt(secretKey, dataToEncrypt);
console.log(encrypted); // { iv: '...', data: '...' }

Example: Decrypting Data

const decrypted = decrypt(secretKey, encrypted.iv, encrypted.data);
console.log(JSON.parse(decrypted)); // { userId: '123', token: 'abc123' }
Note: For production apps, use secure key storage like react-native-keychain or platform-specific secure storage APIs.
Warning: Never hardcode encryption keys in your app. Always use secure key management practices.

Best Practices for Data Encryption

  • Use industry-standard algorithms like AES-256-GCM
  • Store keys securely using platform-specific secure storage (e.g., Android Keystore, iOS Keychain)
  • Never store encryption keys in plain text or local storage
  • Implement proper error handling for decryption failures

4. Using MMKV for Performance

Example: Basic MMKV Setup

import MMKV from 'react-native-mmkv';

const storage = new MMKV();

// Set a value
storage.set('user.name', 'John Doe');

// Get a value
const name = storage.getString('user.name');
console.log(name); // John Doe

// Remove a value
storage.delete('user.name');
Method Description Notes
set(key, value) Sets a value for the given key Supports string, number, boolean, and object values
getString(key) Gets a string value for the given key Returns null if key does not exist
getNumber(key) Gets a number value for the given key Returns null if key does not exist
getBoolean(key) Gets a boolean value for the given key Returns null if key does not exist
getObject(key) Gets an object value for the given key Returns null if key does not exist
delete(key) Deletes a value for the given key No-op if key does not exist
getAllKeys() Gets all keys in the storage Array of strings
clearAll() Clears all values in the storage Removes all keys and their values
Note: MMKV uses a binary format for storing data, which makes it faster than AsyncStorage.
Warning: MMKV is not encrypted by default. For sensitive data, consider using encryption options provided by the library.

MMKV Performance Benefits

  • Fast read/write operations due to binary format
  • Memory-efficient storage
  • Supports large data sets without performance degradation
  • Works well with React Native's native modules

5. Handling Storage Limits

Storage Type Limit Notes
AsyncStorage ~6MB May vary by platform and device
SQLite Unlimited Depends on device storage
File System (React Native) Device storage Subject to OS limits and permissions

Example: Check Storage Usage

import { AsyncStorage } from 'react-native';\n\nconst checkStorageUsage = async () => {\n  try {\n    const keys = await AsyncStorage.getAllKeys();\n    let totalSize = 0;\n    for (const key of keys) {\n      const value = await AsyncStorage.getItem(key);\n      totalSize += new Blob([value]).size;\n    }\n    console.log(`Total storage used: ${totalSize} bytes`);\n  } catch (error) {\n    console.error('Error checking storage:', error);\n  }\n};
Note: AsyncStorage is not suitable for large data sets due to its size limitations. For larger datasets, consider using SQLite or file system storage.
Warning: Exceeding storage limits can cause app crashes or data loss. Always implement checks and cleanup strategies.

6. Implementing Cache Management

Example: Using AsyncStorage for Caching

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

const cacheKey = 'userProfile';
const userData = {
  id: 123,
  name: 'John Doe',
  email: 'john@example.com'
};

// Save to cache
const saveToCache = async () => {
  try {
    await AsyncStorage.setItem(cacheKey, JSON.stringify(userData));
    console.log('Data cached successfully');
  } catch (error) {
    console.error('Failed to cache data:', error);
  }
};

// Retrieve from cache
const getFromCache = async () => {
  try {
    const cachedData = await AsyncStorage.getItem(cacheKey);
    if (cachedData) {
      return JSON.parse(cachedData);
    }
    return null;
  } catch (error) {
    console.error('Failed to retrieve from cache:', error);
    return null;
  }
};
Method Description Notes
setItem(key, value) Saves data to storage Value must be a string
getItem(key) Retrieves data from storage Returns a promise with the string value
removeItem(key) Deletes a key-value pair Removes the item from storage
getAllKeys() Retrieves all keys Useful for cache cleanup
Note: AsyncStorage is not suitable for large data sets or sensitive information. For larger data, consider using SQLite or Realm.

Example: Cache with Expiration

const CACHE_TIMEOUT = 5 * 60 * 1000; // 5 minutes

const setCachedWithExpiry = async (key, value) => {
  const item = {
    value: JSON.stringify(value),
    timestamp: Date.now()
  };
  await AsyncStorage.setItem(key, JSON.stringify(item));
};

const getCachedWithExpiry = async (key) => {
  const cachedItem = await AsyncStorage.getItem(key);
  if (!cachedItem) return null;

  const { value, timestamp } = JSON.parse(cachedItem);
  if (Date.now() - timestamp > CACHE_TIMEOUT) {
    await AsyncStorage.removeItem(key);
    return null;
  }

  return JSON.parse(value);
};

Cache Management Best Practices

  • Use AsyncStorage for simple key-value caching
  • Implement expiration logic to prevent stale data
  • Avoid caching large data sets or sensitive information
  • Consider using react-native-mmkv for better performance

7. Using Secure Storage

Example: Storing and Retrieving Secure Data

import * as SecureStore from 'expo-secure-store';

// Store a value
await SecureStore.setItemAsync('authToken', 'abc123xyz');

// Retrieve a value
const token = await SecureStore.getItemAsync('authToken');
console.log(token); // "abc123xyz"
Method Description Notes
setItemAsync(key, value) Stores a value securely Overwrites existing key
getItemAsync(key) Retrieves a value by key Returns null if key doesn't exist
deleteItemAsync(key) Deletes a stored value No error if key doesn't exist
hasItemAsync(key) Checks if a key exists Returns boolean
Note: SecureStore uses platform-specific secure storage (Keychain on iOS, Keystore on Android). It is encrypted and protected from non-rooted devices.
Warning: Do not store sensitive data in regular AsyncStorage or local storage — use SecureStore for tokens, passwords, or other sensitive values.

8. Migrating Storage Data

Example: Migrating from AsyncStorage to SQLite

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

// Old: AsyncStorage
const oldData = await AsyncStorage.getItem('userPreferences');

// New: SQLite migration
const newData = await new Promise((resolve, reject) => {
  SQLite.openDatabase({name: 'app.db'}).transaction(tx => {
    tx.executeSql(
      'SELECT * FROM userPreferences',
      [],
      (_, results) => resolve(results.rows),
      (_, error) => reject(error)
    );
  });
});
Storage Type Migration Path Notes
AsyncStorage react-native-async-storage Legacy, limited performance
SQLite react-native-sqlite-storage Structured, scalable
Realm realm Object-oriented, real-time sync

Migration Strategy

  • Export data from old storage format
  • Transform and validate data structure
  • Write to new storage with error handling
  • Remove old storage references after success
Warning: Always backup user data before migration to prevent data loss.
Note: Use versioned schemas to handle future data structure changes.

9. Clearing Storage

Example: Clearing AsyncStorage

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

// Clear all storage
await AsyncStorage.clear();

// Clear specific key
await AsyncStorage.removeItem('userToken');
Method Description Notes
AsyncStorage.clear() Removes all keys and values Asynchronous operation
AsyncStorage.removeItem(key) Removes a specific key-value pair Key must exist or no-op
Note: Clearing storage is irreversible and should be used carefully in production.
Warning: This action will permanently delete all stored data, including user preferences and session tokens.

10. Implementing Storage Listeners

Example: Setting up a storage listener with Firebase

import { database } from './firebaseConfig';

const ref = database.ref('users');
ref.on('value', (snapshot) => {
  const data = snapshot.val();
  console.log('User data updated:', data);
});

// To remove the listener later
ref.off('value');
Method Description Parameters Notes
on(eventType, callback) Registers a listener for a specific event type eventType: string, callback: function Triggers when data changes at the specified location
off(eventType, callback) Removes a listener for a specific event type eventType: string, callback: function (optional) If no callback is provided, all listeners for that event type are removed
once(eventType, callback) Registers a listener that fires once and then removes itself eventType: string, callback: function Useful for initial data fetches
Note: Firebase listeners are automatically cleaned up when the component unmounts in React Native apps. It's good practice to explicitly remove listeners to prevent memory leaks.

Was this article helpful?