DEV Community

Ajmal Hasan
Ajmal Hasan

Posted on • Edited on

Replace Async Storage with MMKV in React Native

React-Native MMKV

  • The fastest key/value storage for React Native.

  • MMKV is an efficient, small mobile key-value storage framework developed by WeChat.

  • react-native-mmkv is a library that allows you to easily use MMKV inside your React Native applications. It provides fast and direct bindings to the native C++ library which are accessible through a simple JS API.


Features

  1. Get and set strings, booleans and numbers
  2. Fully synchronous calls, no async/await, no Promises, no Bridge.
  3. Encryption support (secure storage)
  4. Multiple instances support (separate user-data with global data)
  5. Customize storage location
  6. High performance because everything is written in C++
  7. ~30x faster than AsyncStorage
  8. Uses JSI instead of the "old" Bridge
  9. iOS, Android and Web support
  10. Easy to use React Hooks API
  11. No Async/Await

Limitations:

Blog

Image description
Click here to compare popular storage solutions for React Native.


Installation

yarn add react-native-mmkv && cd ios && pod install
Enter fullscreen mode Exit fullscreen mode

or

npm install react-native-mmkv && cd ios && pod install
Enter fullscreen mode Exit fullscreen mode

Click here to check how to store/retrieve values in app

Click here to see how to use it as an hook like useState

Click here to add listener when ever some key changes and you want to perform some action on that

CLick here to see how to integrate MMKV with redux-persist

Click here to use reactotron-react-native-mmkv to automatically log writes to your MMKV storage using Reactotron.


Usage with redux persist and reactotron

You should re-use this instance throughout your entire app instead of creating a new instance each time, so export the storage object.

  1. Make a MMKVStorage.js helper file and paste the below code:

MMKVStorage.js

import { MMKV } from 'react-native-mmkv';
import { ACCESS_TOKEN, BIOMETRICS_LOGGED_IN } from './Constants';

export const storage = new MMKV();

/*
//Encrypted storage
export const storage = new MMKV({
  id: `user-${userId}-storage`, //required: if when either path/encryptionKey exist
  path: `${USER_DIRECTORY}/storage`, //optional: changing storage path
  encryptionKey: 'hunter2' //optional: storing all values encrypted
})
*/

//TO BE USED IN REDUX PERSIST
export const reduxPersistStorage = {
  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.delete(key);
    return Promise.resolve();
  },
};

//HELPER FUNCTIONS TO BE USED THROUGHOUT APP
export const StorageMMKV = {
  setUserPreferences: (key, value) => {
    try {
      storage.set(`${key}`, `${value}`);
    } catch (error) {
      console.error('Error setting user preferences:', error);
    }
  },

  getUserPreferences: (key) => {
    try {
      return storage.getString(key);
    } catch (error) {
      console.error('Error getting user preferences:', error);
      return null; // Or handle the error according to your application's logic
    }
  },

  removeItem: (key) => {
    try {
      storage.delete(key);
    } catch (error) {
      console.error('Error removing item:', error);
    }
  },

  clearAll: () => {
    try {
      storage.clearAll();
    } catch (error) {
      console.error('Error clearing storage:', error);
    }
  },

  setLoggedInByBiometric: (val) => {
    try {
      if (val) {
        storage.set(`${BIOMETRICS_LOGGED_IN}`, `${val}`);
      } else {
        storage.delete(BIOMETRICS_LOGGED_IN);
      }
    } catch (error) {
      console.error('Error setting logged in by biometric:', error);
    }
  },

  addRemoveDeviceToken: (val) => {
    try {
      if (val) {
        storage.set(`${ACCESS_TOKEN}`, `${val}`);
      } else {
        storage.delete(ACCESS_TOKEN);
      }
    } catch (error) {
      console.error('Error adding/removing device token:', error);
    }
  },
};

Enter fullscreen mode Exit fullscreen mode

2 . Create a ReactotronConfig.js file:

ReactotronConfig.js

import Reactotron from 'reactotron-react-native';
import { reactotronRedux } from 'reactotron-redux';
import mmkvPlugin from 'reactotron-react-native-mmkv';
import { MMKV } from 'react-native-mmkv';

//hack to avoid multiple reconnection of device on refresh
const REACTOTRON_ASYNC_CLIENT_ID = '@REACTOTRON/clientId';
const storage = new MMKV({ id: REACTOTRON_ASYNC_CLIENT_ID });
const getClientId = async () => storage.getString(REACTOTRON_ASYNC_CLIENT_ID);
const setClientId = async (clientId) => {
  storage.set(REACTOTRON_ASYNC_CLIENT_ID, clientId);
};

Reactotron.configure({
  getClientId,
  setClientId,
})
  .useReactNative()
  .use(reactotronRedux()) //  <- here i am!
  .use(
    mmkvPlugin({
      storage, // mmkv instance
      ignore: ['secret', 'persist:root'],
      // "persist:root" key will avoid showing redux persist update every time redux persist
    })
  )
  .connect();

const yeOldeConsoleLog = console.log;
// make a new one
console.log = (...args) => {
  // always call the old one, because React Native does magic swizzling too
  yeOldeConsoleLog(...args);
  // send this off to Reactotron.
  Reactotron.display({
    name: 'CONSOLE',
    value: args,
    preview: args.length > 0 && typeof args[0] === 'string' ? args[0] : null,
  });
};

export default Reactotron;
Enter fullscreen mode Exit fullscreen mode

3 . Update your store.js like this for reactotron and mmkv configuration:

store.js

import { combineReducers, configureStore } from '@reduxjs/toolkit';
import { persistReducer, FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER } from 'redux-persist';
import userSlice from './slices/userSlice';
import nonPersistedSlice from './slices/nonPersistedSlice';
import { reduxPersistStorage } from '../helpers/MMKVStorage';

const persistConfig = {
  key: 'root',
  version: 1,
  storage: reduxPersistStorage, // MMKVStorage,
  blacklist: ['nonPersistedSlice'], // these reduce will not persist data
  whitelist: ['userSlice'], // these reduce will persist data
};
const reducer = combineReducers({
  userSlice,
  nonPersistedSlice,
});
const persistedReducer = persistReducer(persistConfig, reducer);

const getEnhancers = (getDefaultEnhancers) => {
  if (process.env.NODE_ENV === 'development') {
    const reactotron = require('../helpers/ReactotronConfig').default;
    return getDefaultEnhancers().concat(reactotron.createEnhancer());
  }
  return getDefaultEnhancers();
};

// https://redux-toolkit.js.org/usage/usage-guide#use-with-redux-persist
const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], //
      },
    }),
  enhancers: getEnhancers, // REACTOTRON CONFIGURATION
});

export default store;

Enter fullscreen mode Exit fullscreen mode

4 . Finally update your App.jsx:

App.js

// /**
//  * Sample React Native App
//  * https://github.com/facebook/react-native
//  *
//  * @format
//  * @flow strict-local
//  */

import React from 'react';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { persistStore } from 'redux-persist';
// import store from './redux/store';
import rtkStore from './reduxToolkit/store';
import HomeScreen from './screens/home';

let persistor = persistStore(rtkStore);

const App = () => {

  const getASBiometricValue = async () => {

    const bv = await StorageMMKV.getUserPreferences(BIOMETRICS_LOGGED_IN);
    setIsSelectedBiometric(bv);
  };

  useEffect(() => {
getASBiometricValue(
  }, []);

  return (
    <Provider store={rtkStore}>
      <PersistGate loading={null} persistor={persistor}>
        <HomeScreen />
      </PersistGate>
    </Provider>
  );
};
export default App;
Enter fullscreen mode Exit fullscreen mode

Source Code Github Link - >

Top comments (5)

Collapse
 
med5 profile image
Mohamed Lamine Badji

Did you ever face this error: Error: Failed to create a new MMKV instance: The native MMKV Module could not be found.?
If yes, how did you fix it?
I'm struggling to find out a fix even after checking StackOverflow and github issues

Collapse
 
ajmal_hasan profile image
Ajmal Hasan • Edited

If not using new arch then downgrade MMKV to 2.12.2 version

Collapse
 
ducsilva profile image
DucSilva • Edited

How to share key storage to from another app?

Collapse
 
madhu_avinash_5780adb59d5 profile image
Madhu Avinash

How does react-native-mmkv achieve syncrhonus while being on react-native old architecture?

Collapse
 
ajmal_hasan profile image
Ajmal Hasan

It stores values in RAM. So very huge data should be avoided for storing in mmkv