dist/plugins/mobile-storage-mmkv/skills/mobile-storage-mmkv/SKILL.md
MMKV high-performance key-value storage for React Native - synchronous JSI-based reads/writes, encryption, typed hooks, multiple instances, listeners, persistence middleware adapters
npx skillsauth add agents-inc/skills mobile-storage-mmkvInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Quick Guide: Use
createMMKV()for synchronous key-value storage (~30x faster than AsyncStorage). One singleton instance per concern (global app, per-user). Use typed hooks (useMMKVString,useMMKVObject) for reactive components. Enable encryption withencryptionKeyfor sensitive data. V4 is a Nitro Module requiringreact-native-nitro-modulesand React Native 0.75+.
<critical_requirements>
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering,
import type, named constants)
(You MUST reuse a single MMKV instance per concern -- NEVER call createMMKV() on every render or in component bodies)
(You MUST use typed getters (getString, getNumber, getBoolean) -- NEVER parse the return value of the wrong getter)
(You MUST use remove() to delete keys -- delete() was renamed in v4 due to C++ keyword conflict)
(You MUST install react-native-nitro-modules alongside react-native-mmkv -- v4 is a Nitro Module)
</critical_requirements>
Auto-detection: MMKV, react-native-mmkv, createMMKV, useMMKVString, useMMKVNumber, useMMKVBoolean, useMMKVObject, useMMKVBuffer, useMMKVListener, useMMKVKeys, addOnValueChangedListener, encryptionKey, mmkv storage, key-value storage React Native
When to use:
Key patterns covered:
createMMKV() and configuration optionsuseMMKVString, useMMKVObject, etc.)addOnValueChangedListener, useMMKVListener)StateStorage interface)When NOT to use:
Detailed Resources:
MMKV is a synchronous, JSI-based key-value store built on top of Tencent's battle-tested C++ library. The key advantage over AsyncStorage is that reads and writes are synchronous -- no await, no Promises, no bridge serialization. This eliminates an entire class of race conditions and simplifies code.
Core principles:
getString() returns immediately, no async wrappers neededencryptionKey optionuseMMKVString etc. trigger re-renders on changes, replacing manual subscriptionsPerformance comparison with AsyncStorage:
| Operation | AsyncStorage | MMKV | Speedup | |----------------|-------------|--------|---------| | Read 1 key | ~5ms | ~0.015ms | ~300x | | Write 1 key | ~8ms | ~0.018ms | ~440x | | Read 1000 keys | ~200ms | ~3ms | ~65x |
Benchmarks vary by device, but MMKV is consistently 30-100x faster for typical operations.
V4 architecture: MMKV v4 is a Nitro Module (not a TurboModule). This means it uses react-native-nitro-modules for the native bridge, requires React Native 0.75+, and the JS API uses createMMKV() instead of new MMKV().
Create one instance per storage concern at module scope. Never inside a component or hook body.
import { createMMKV } from "react-native-mmkv";
// Global app storage -- reuse this everywhere
export const storage = createMMKV();
// Named instance for user-specific data
export const createUserStorage = (userId: string) =>
createMMKV({ id: `user-${userId}` });
Why good: Module-level creation runs once, all consumers share the same native instance, no wasted allocations
// BAD: Creating instance inside component
function Settings() {
const storage = createMMKV(); // New native instance every render
// ...
}
Why bad: Creates a new native MMKV instance on every render, wastes memory, defeats instance caching
See examples/core.md for full configuration options (path, encryption, readOnly, compareBeforeSet).
MMKV stores values by type. Always use the matching getter for what was stored.
// Set typed values
storage.set("user.name", "Alice");
storage.set("user.age", 28);
storage.set("onboarded", true);
// Get with correct typed getter
const name = storage.getString("user.name"); // string | undefined
const age = storage.getNumber("user.age"); // number | undefined
const done = storage.getBoolean("onboarded"); // boolean | undefined
Why good: Each getter returns the correct type or undefined if key is missing -- no parsing, no type confusion
Gotcha: Calling getString on a key that was stored with set(key, number) returns undefined, not a stringified number. MMKV does not auto-convert between types.
See examples/core.md for object serialization with JSON.stringify/JSON.parse and ArrayBuffer storage.
Hooks provide useState-like API backed by MMKV. Components re-render when the stored value changes.
import { useMMKVString, useMMKVBoolean, useMMKVObject } from "react-native-mmkv";
import type { User } from "../types";
function ProfileScreen() {
const [name, setName] = useMMKVString("user.name");
const [darkMode, setDarkMode] = useMMKVBoolean("settings.darkMode");
const [user, setUser] = useMMKVObject<User>("user.profile");
// Set undefined to delete the key
const clearProfile = () => setUser(undefined);
}
Why good: Reactive re-renders on change, type-safe generics for objects, setting undefined removes the key
Custom instance: Pass instance as second argument: useMMKVString("key", userStorage)
See examples/core.md for all hook variants including useMMKVBuffer and useMMKVKeys.
Listen to storage changes outside React components (background tasks, services, cross-instance sync).
const listener = storage.addOnValueChangedListener((changedKey) => {
const newValue = storage.getString(changedKey);
console.log(`${changedKey} changed to: ${newValue}`);
});
// Cleanup when no longer needed
listener.remove();
Why good: Works outside React tree, receives the changed key (read new value yourself), cleanup via .remove()
For React components, prefer useMMKVListener hook -- it handles cleanup automatically.
See examples/core.md for useMMKVListener hook usage.
Use separate named instances to isolate data by concern. Common pattern: one global instance, one per logged-in user.
const APP_STORAGE_ID = "app-global";
export const appStorage = createMMKV({ id: APP_STORAGE_ID });
export const createUserStorage = (userId: string) =>
createMMKV({ id: `user-${userId}` });
// On logout: delete user-specific storage entirely
import { deleteMMKV } from "react-native-mmkv";
const handleLogout = (userId: string) => {
deleteMMKV(`user-${userId}`);
};
Why good: User data is fully isolated from app data, deleteMMKV removes the entire instance on logout
See examples/advanced.md for existsMMKV checks and instance lifecycle management.
Enable AES encryption for sensitive data. Encryption applies to the entire instance -- you cannot encrypt individual keys.
// Instance with AES-256 encryption
const secureStorage = createMMKV({
id: "secure",
encryptionKey: "your-encryption-key",
encryptionType: "AES-256",
});
// Encrypt/decrypt existing instance at runtime
storage.encrypt("new-password", "AES-256");
storage.decrypt(); // Remove encryption
When to use: Auth tokens, API keys, PII, anything that should not be readable if device is compromised
See examples/advanced.md for key rotation patterns and encryption type comparison.
Bridge MMKV with state management persistence middleware by implementing a StateStorage-compatible interface.
import { createMMKV } from "react-native-mmkv";
const storage = createMMKV();
// Implement the StateStorage interface your persist middleware expects
interface StateStorage {
setItem: (name: string, value: string) => void;
getItem: (name: string) => string | null;
removeItem: (name: string) => void;
}
export const mmkvStateStorage: StateStorage = {
setItem: (name, value) => storage.set(name, value),
getItem: (name) => storage.getString(name) ?? null,
removeItem: (name) => storage.remove(name),
};
Why good: Synchronous adapter eliminates async overhead, drop-in replacement for AsyncStorage adapters, works with any persist middleware that accepts StateStorage
See examples/persistence.md for complete persistence middleware setup with hydration handling.
</patterns><decision_framework>
What kind of data are you storing?
|
+-> Key-value pairs (strings, numbers, booleans, small objects)?
| +-> Sensitive data (tokens, keys, PII)?
| | +-> YES -> MMKV with encryptionKey
| | +-> NO -> MMKV without encryption
| +-> Need reactive UI updates?
| | +-> YES -> Use MMKV hooks (useMMKVString, etc.)
| | +-> NO -> Use direct get/set API
| +-> Multiple users or data domains?
| +-> YES -> Multiple named instances
| +-> NO -> Single default instance
|
+-> Large files or binary media?
| +-> Use the filesystem (not MMKV)
|
+-> Relational data with queries?
| +-> Use a local database (not MMKV)
|
+-> Server-cached data with invalidation?
+-> Use your data fetching layer (not MMKV)
| Scenario | API |
|---------------------------------|-----------------------------|
| Read/write in services or utils | Direct: storage.getString() |
| Reactive component state | Hook: useMMKVString() |
| Cross-component sync | Hook or addOnValueChangedListener |
| Background task or service | Direct + listener |
| State management persistence | StateStorage adapter |
</decision_framework>
<red_flags>
High Priority Issues:
createMMKV() inside a component body -- creates new native instance every render, use module-scope singletonstorage.delete() -- renamed to storage.remove() in v4, delete is a C++ reserved keywordgetString on a number key returns undefined, not a string. Use the matching getter.react-native-nitro-modules peer dependency -- v4 crashes at runtime without itMedium Priority Issues:
listener.remove() -- native listeners leak if not cleaned upencryptionType: "AES-256" when AES-256 is needed -- default is AES-128Gotchas & Edge Cases:
useMMKVObject<T> uses JSON.stringify/JSON.parse internally -- objects with Date, Map, Set lose their typesundefined deletes the key from storage -- intentional API, not a bugcompareBeforeSet option prevents writing if value is unchanged -- useful for reducing disk I/O in high-frequency updatesAppGroupIdentifier in Info.plist (was AppGroup in v3) and mode: "multi-process"createMMKV() works in test runners without native compilationgetAllKeys() returns all keys as an array -- there is no prefix filtering, implement it yourself if neededstorage.size returns bytes used -- call storage.trim() to reclaim space from deleted keys</red_flags>
<critical_reminders>
All code must follow project conventions in CLAUDE.md
(You MUST reuse a single MMKV instance per concern -- NEVER call createMMKV() on every render or in component bodies)
(You MUST use typed getters (getString, getNumber, getBoolean) -- NEVER parse the return value of the wrong getter)
(You MUST use remove() to delete keys -- delete() was renamed in v4 due to C++ keyword conflict)
(You MUST install react-native-nitro-modules alongside react-native-mmkv -- v4 is a Nitro Module)
Failure to follow these rules will cause memory leaks, runtime crashes, or silent data loss.
</critical_reminders>
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Xquik REST API patterns for X post search, user and timeline reads, cursor pagination, media downloads, monitors, signed webhooks, and approval-gated X actions
development
Mapbox GL JS interactive maps - map initialization, markers, popups, sources, layers, expressions, clustering, 3D terrain, geocoding, directions
tools
Leaflet interactive maps - map setup, tile layers, markers, popups, GeoJSON, custom controls, plugins, clustering, events