react-native-mmkv

repository·main·Indexed 27 days ago

https://github.com/mrousavy/react-native-mmkv

A high-performance, small mobile key-value storage framework providing fast, synchronous JS bindings to the native C++ MMKV library using JSI. It includes support for encryption (AES-128/256), ArrayBuffers, and a suite of React hooks for reactive state management, such as useMMKVString, useMMKVNumber, and useMMKVObject. Designed as a significantly faster alternative to AsyncStorage.

Tokens
11.3K
Snippets
36
Records
59
Agent score
91%

What's inside react-native-mmkv

  1. Use MMKV as a Recoil storage wrapper

    main

    To persist Recoil atoms using MMKV, implement a persistAtom function that uses an MMKV storage instance. This function handles initializing the atom state from MMKV via setSelf and updating MMKV whenever the atom value changes or is reset via onSet.

    const persistAtom = (key) => ({ setSelf, onSet }) => {
      setSelf(() => {
        let data = storage.getString(key);
        if (data != null){
          return JSON.parse(data);
        } else {
          return new DefaultValue();
        }
      });
    
      onSet((newValue, _, isReset) => {
        if (isReset) {
          storage.remove(key);
        } else {
          storage.set(key, JSON.stringify(newValue));
        }
      });
    };
  2. Integrate react-query with react-native-mmkv

    main

    To use MMKV as a persistence layer for TanStack Query (react-query), you need to install the necessary persistence packages, create a compatible storage adapter, and wrap your application with PersistQueryClientProvider.

    1. Install dependencies

    Install the async storage persister and the persist client packages:

    yarn add @tanstack/query-async-storage-persister @tanstack/react-query-persist-client

    2. Create the MMKV storage adapter

    Since react-query expects an Async Storage-like interface, create a clientStorage object that maps setItem, getItem, and removeItem to MMKV methods. Note that getItem must return null instead of undefined to be compatible.

    3. Configure the PersistQueryClientProvider

    Use the created clientPersister within the persistOptions prop of the PersistQueryClientProvider in your root component.

    import { createAsyncStoragePersister } from '@tanstack/query-async-storage-persister'
    import { createMMKV } from "react-native-mmkv"
    import { PersistQueryClientProvider } from '@tanstack/react-query-persist-client'
    
    // 1. Initialize MMKV
    const storage = createMMKV();
    
    // 2. Create the adapter
    const clientStorage = {
      setItem: (key, value) => {
        storage.set(key, value);
      },
      getItem: (key) => {
        const value = storage.getString(key);
        return value === undefined ? null : value;
      },
      removeItem: (key) => {
        storage.remove(key);
      },
    };
    
    // 3. Create the persister
    export const clientPersister = createAsyncStoragePersister({ storage: clientStorage });
    
    // 4. Use in your App
    const App = () => {
      return (
        <PersistQueryClientProvider persistOptions={{ persister: clientPersister }}>
          {/* Your App Content */}
        </PersistQueryClientProvider>
      );
    };
  3. Use MMKV with Zustand persist middleware

    main

    To use react-native-mmkv as the storage engine for Zustand's persist middleware, you must create a StateStorage object that maps Zustand's storage interface to MMKV's methods.

    1. Create an MMKV instance using createMMKV().
    2. Implement the StateStorage interface with setItem, getItem, and removeItem.
    3. Use storage.set(name, value) for setItem.
    4. Use storage.getString(name) for getItem, ensuring you return null if the value is undefined.
    5. Use storage.remove(name) for removeItem.
    import { StateStorage } from 'zustand/middleware'
    import { createMMKV } from 'react-native-mmkv'
    
    const storage = createMMKV()
    
    const zustandStorage: StateStorage = {
      setItem: (name, value) => {
        return storage.set(name, value)
      },
      getItem: (name) => {
        const value = storage.getString(name)
        return value ?? null
      },
      removeItem: (name) => {
        return storage.remove(name)
      },
    }
  4. Integrate react-native-mmkv with mobx-persist-store

    main

    To use MMKV as the storage engine for mobx-persist-store, you must provide a custom storage object to the configurePersistable function. This object maps the expected mobx-persist-store methods (setItem, getItem, and removeItem) to the corresponding react-native-mmkv instance methods (set, getString, and remove).

    import { configurePersistable } from 'mobx-persist-store'
    import { createMMKV } from "react-native-mmkv"
    
    const storage = createMMKV()
    
    configurePersistable({
      storage: {
        setItem: (key, data) => storage.set(key, data),
        getItem: (key) => storage.getString(key),
        removeItem: (key) => storage.remove(key),
      },
    })
  5. Use MMKV with Jotai via atomWithMMKV

    main

    To persist Jotai atoms using MMKV, you must implement a custom atomWithMMKV function. This function leverages Jotai's atomWithStorage and createJSONStorage utilities, wrapping MMKV's getString, set, remove, and addOnValueChangedListener methods.

    createJSONStorage automatically handles JSON.stringify() when saving and JSON.parse() when retrieving values, allowing you to store complex objects directly.

    Once implemented, use atomWithMMKV(key, initialValue) instead of the standard atom(initialValue) to ensure the state persists across app restarts.

    import { atomWithStorage, createJSONStorage } from 'jotai/utils';
    import { createMMKV } from 'react-native-mmkv';
    
    const storage = createMMKV();
    
    function getItem(key: string): string | null {
      const value = storage.getString(key)
      return value ? value : null
    }
    
    function setItem(key: string, value: string): void {
      storage.set(key, value)
    }
    
    function removeItem(key: string): void {
      storage.remove(key);
    }
    
    function subscribe(
      key: string,
      callback: (value: string | null) => void
    ): () => void {
      const listener = (changedKey: string) => {
        if (changedKey === key) {
          callback(getItem(key))
        }
      }
    
      const { remove } = storage.addOnValueChangedListener(listener)
    
      return () => {
        remove()
      }
    }
    
    export const atomWithMMKV = <T>(key: string, initialValue: T) =>
      atomWithStorage<T>(
        key,
        initialValue,
        createJSONStorage<T>(() => ({
          getItem,
          setItem,
          removeItem,
          subscribe,
        })),
        { getOnInit: true }
      );
    
    // Usage:
    const myAtom = atomWithMMKV('my-atom-key', 'value');
  6. Install react-native-mmkv

    main

    To install react-native-mmkv in a standard React Native project, you must also install react-native-nitro-modules. After installing via npm, ensure you run pod install for iOS.

    npm install react-native-mmkv react-native-nitro-modules
    cd ios && pod install
  7. Use MMKV with Tinybase

    main

    To use MMKV as a persistence layer for Tinybase, you need to use the createReactNativeMmkvPersister from the tinybase/persisters/persister-react-native-mmkv package. This requires an instance of MMKV created via createMMKV() from react-native-mmkv and a Tinybase store.

    import { createMMKV } from 'react-native-mmkv'
    import { createStore } from 'tinybase';
    import { createReactNativeMmkvPersister } from 'tinybase/persisters/persister-react-native-mmkv';
    
    const storage = createMMKV()
    const store = createStore().setTables({ pets: { fido: { species: 'dog' } } });
    const persister = createReactNativeMmkvPersister(store, storage);
    
    await persister.save();
  8. Migrate data from AsyncStorage to MMKV

    main

    To migrate existing data from @react-native-async-storage/async-storage to react-native-mmkv, you can implement a migration script that iterates through all keys in AsyncStorage, copies their values to MMKV, and then removes them from AsyncStorage.

    Note that AsyncStorage stores everything as strings. When migrating, you should handle boolean conversion (e.g., checking if a string is 'true' or 'false') to ensure data types are preserved correctly in MMKV. It is recommended to use a flag like hasMigratedFromAsyncStorage in MMKV to ensure the migration process only runs once.

    import AsyncStorage from '@react-native-async-storage/async-storage';
    import { createMMKV } from 'react-native-mmkv';
    
    export const storage = createMMKV();
    
    export const hasMigratedFromAsyncStorage = storage.getBoolean(
      'hasMigratedFromAsyncStorage',
    );
    
    export async function migrateFromAsyncStorage(): Promise<void> {
      const keys = await AsyncStorage.getAllKeys();
    
      for (const key of keys) {
        try {
          const value = await AsyncStorage.getItem(key);
    
          if (value != null) {
            // Handle boolean conversion from AsyncStorage strings
            if (['true', 'false'].includes(value)) {
              storage.set(key, value === 'true');
            } else {
              storage.set(key, value);
            }
    
            await AsyncStorage.removeItem(key);
          }
        } catch (error) {
          console.error(`Failed to migrate key "${key}" from AsyncStorage to MMKV!`, error);
          throw error;
        }
      }
    
      storage.set('hasMigratedFromAsyncStorage', true);
    }
  9. Install react-native-mmkv in Expo

    main

    For Expo projects, use npx expo install to ensure compatible versions of react-native-mmkv and react-native-nitro-modules, then run npx expo prebuild to configure the native modules.

    npx expo install react-native-mmkv react-native-nitro-modules
    npx expo prebuild
  10. Use MMKV as a storage engine for redux-persist

    main

    To use react-native-mmkv with redux-persist, you must create a custom Storage object that wraps the MMKV instance. This wrapper maps the asynchronous redux-persist API (setItem, getItem, removeItem) to the synchronous MMKV methods (set, getString, remove), returning Promise.resolve() to satisfy the redux-persist interface.

    import { Storage } from 'redux-persist'
    import { createMMKV } from "react-native-mmkv"
    
    const storage = createMMKV()
    
    export const reduxStorage: Storage = {
      setItem: (key, value) => {
        storage.set(key, value)
        return Promise.resolve(true)
      },
      getItem: (key) => {
        const value = storage.getString(key)
        return Promise.resolve(value)
      },
      removeItem: (key) => {
        storage.remove(key)
        return Promise.resolve()
      },
    }