Without a local persistence library, your app stores all data in memory — which evaporates the moment the OS terminates the process. Users lose cart contents, form drafts, or session state on every background cycle. On mobile, the OS kills backgrounded apps aggressively; an app with zero local storage is effectively stateless. ISO 25010:2011 reliability.recoverability scores zero when there is nothing to recover. Beyond UX frustration, this is an architectural gap: offline-first patterns, crash recovery, and conflict resolution all presuppose that at least one store/retrieve call site exists.
Critical because a total absence of local persistence means every app restart silently destroys user data with no recovery path.
Add AsyncStorage as your baseline persistence layer, or MMKV when performance matters. Install the library, then wire at least two distinct store/retrieve call sites — one for writes, one for reads — before marking this resolved.
// npm install @react-native-async-storage/async-storage
import AsyncStorage from '@react-native-async-storage/async-storage';
export const save = async (key: string, value: unknown) => {
await AsyncStorage.setItem(key, JSON.stringify(value));
};
export const load = async <T>(key: string): Promise<T | null> => {
const raw = await AsyncStorage.getItem(key);
return raw ? (JSON.parse(raw) as T) : null;
};
For high-throughput writes, swap in MMKV: npm install react-native-mmkv, then new MMKVLoader().initialize(). Keep sensitive data out of both — use expo-secure-store or Keychain for tokens (see encryption-at-rest).
ID: mobile-offline-storage.data-persistence.storage-library-present
Severity: critical
What to look for: Examine whether the app explicitly imports and uses a local storage library. Look for imports of AsyncStorage from @react-native-async-storage/async-storage, MMKV from react-native-mmkv, expo-sqlite, watermelondb, or realm. Check for usage patterns in components, screens, or dedicated storage/persistence modules.
Pass criteria: Count all storage library imports found across the codebase. At least 1 persistent storage library is imported and actively used with at least 2 distinct store/retrieve call sites. Usage pattern must demonstrate actual data being stored/retrieved (not just imported but unused). Report even on pass: "Found X storage library imports across Y files with Z store/retrieve call sites."
Fail criteria: No local storage library is imported, or imports exist but no usage of the library is found in the codebase. Do NOT pass when the library is imported but only used in test files or commented-out code.
Skip (N/A) when: Never — every mobile app needs local data persistence.
Cross-reference: The App Store Readiness audit (mobile-store-readiness) checks build configuration that affects how storage libraries are bundled for release.
Detail on fail: Specify which storage solutions are missing. Example: "AsyncStorage imported but never used. No other storage library found. App relies entirely on in-memory state."
Remediation: Your app needs a way to persist data locally so users don't lose progress when the app is closed or the device loses power. AsyncStorage is the most common choice:
// Install: npm install @react-native-async-storage/async-storage
import AsyncStorage from '@react-native-async-storage/async-storage';
const storeUserData = async (userId, userData) => {
try {
await AsyncStorage.setItem(`user_${userId}`, JSON.stringify(userData));
} catch (error) {
console.error('Failed to store data:', error);
}
};
const getUserData = async (userId) => {
try {
const data = await AsyncStorage.getItem(`user_${userId}`);
return data ? JSON.parse(data) : null;
} catch (error) {
console.error('Failed to retrieve data:', error);
return null;
}
};
For higher performance with large datasets, consider MMKV:
// Install: npm install react-native-mmkv
import { MMKVLoader } from 'react-native-mmkv';
const storage = new MMKVLoader().initialize();
storage.setString('userId', userId);
const stored = storage.getString('userId');