Stop Using React Native Async Storage

A Comparison of React Native MMKV and React Native Async Storage

Tarik
Level Up Coding

--

Photo by regularguy.eth on Unsplash

For quite some time, React Native Async Storage has been the go-to option for storing data in React Native applications. However, the introduction of React-Native MMKV presents developers with a more efficient and advanced choice.

MMKV, originally designed as an efficient, small, and user-friendly mobile key-value storage framework for the WeChat app.

It brings its fast, direct bindings to the native C++ library to the React Native platform through a simple JavaScript API. React-Native MMKV is now a prominent option for data storage in React Native applications. One of the key advantages of React-Native MMKV is its performance. With everything written in C++, it is ~30x faster than AsyncStorage. Furthermore, it also provides encryption support, making it a secure storage solution for your app. The use of JSI instead of the “old” Bridge further enhances its speed and efficiency.

Benchmark

MMKV vs other storage libraries: Reading a value from Storage 1000 times.
Measured in milliseconds on an iPhone 11 Pro, lower is better.

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

No Async/Await

React-Native MMKV also offers fully synchronous calls, making it easy to use without async/await or Promises. This is in contrast to AsyncStorage, which requires asynchronous behavior. With react-native-mmkv, you can easily store and retrieve data using simple getters and setters. Let’s see how we can initialize our storage using react-native-mmkv:

import { MMKV } from 'react-native-mmkv'

export const appStorage = new MMKV({
id: 'my-app-storage',
})

Retrieving data from storage is also just as straightforward, as shown below:

// set a new key/value pair using react-native-mmkv
appStorage.set('username', 'John')
const username = appStorage.getString('username')

// below is how you would do using react-native-async-storage
await AsyncStorage.setItem('username', 'John')
const username = await AsyncStorage.getItem('username')

React-Native MMKV also provides support for object storage, making it easy to store complex data structures. This is shown in the following code:

// Objects 

// using react-native-mmkv

const user = {
username: 'John',
role: 'Admin'
}

appStorage.set('user', JSON.stringify(user))

const jsonUser = appStorage.getString('user')
const userObject = JSON.parse(jsonUser)

// using react-native-async-storage

await AsyncStorage.setItem('user', JSON.stringify(user))
const jsonUser = await AsyncStorage.getItem('user')
const userObject = JSON.parse(jsonUser)

Removing items from storage is just as easy, as demonstrated below:

// to remove specific item from the storage in mmkv
appStorage.delete('username')

// below is how you would do using react-native-async-storage
await AsyncStorage.removeItem('username')

Real life example - Zustand middleware-persist and React Native MMKV

React-Native MMKV has the ability to integrate with popular state management libraries such as jotai, redux-persist, mobx-persist, and zustand-persist-middleware. This allows developers to leverage the power of MMKV’s efficient, fast, and easy-to-use storage capabilities with their existing state management solutions. The integration results in a seamless and streamlined state management experience, making it easier for developers to manage and persist their application data.

import { Appearance } from "react-native";
import { MMKV } from "react-native-mmkv";
import create from "zustand";
import { persist, StateStorage } from "zustand/middleware";

type AppPersistStore = {
isDarkTheme: boolean;
setIsDarkTheme: (preference: boolean) => void;
};

export const appPersistStorage = new MMKV({ id: "app-persist-storage" });

const zustandMMKVStorage: StateStorage = {
setItem: (name, value) => {
return appPersistStorage.set(name, value);
},
getItem: (name) => {
const value = appPersistStorage.getString(name);
return value ?? null;
},
removeItem: (name) => {
return appPersistStorage.delete(name);
},
};

export const useAppPersistStore = create<
AppPersistStore,
[["zustand/persist", AppPersistStore]]
>(
persist(
(set, get) => ({
isDarkTheme: Appearance.getColorScheme() === "dark",
setIsDarkTheme: (preference) => set({ isDarkTheme: preference }),
}),
{
name: "app-persist-storage",
getStorage: () => zustandMMKVStorage,
serialize: (state) => JSON.stringify(state),
deserialize: (state) => JSON.parse(state),
},
),
);

The code snippet above demonstrates the use of the zustand-persist-middleware with react-native-mmkv for handling the app's light/dark theme. The combination of these two libraries makes it possible to persist the app's state, even after the app has been closed or restarted.

We declare a custom type, AppPersistStore, which contains information about the app's theme preference and a method for changing that preference.

Next, we create an instance of the MMKV instance, which will be used to persist the state of our app. The instance is stored in a constant called appPersistStorage.

We then create an object, zustandMMKVStorage, that implements the StateStorage interface. This object is responsible for providing a set of methods that allow us to interact with the appPersistStorage instance, such as setting and retrieving values.

Finally, we use the create function from the zustand library to create a hook called useAppPersistStore, which returns the current state of the app's theme preference, as well as a method for changing that preference. We use the persist middleware to ensure that the state of our app is automatically saved to the appPersistStorage instance whenever the state changes.

Whenever we call any action from this store, it will be persisted, which means that even if the app is closed or restarted, the state of the app will remain the same as it was when the app was last used. This helps to provide a consistent experience for the user, regardless of when or how the app is used.

Below is how we would end up using this hook:

import * as React from "react";
import { Button } from "react-native";
import { useAppPersistStore } from "./app-persist";

const Home: React.FC = () => {
// here we have our mmkv and zustand persist-middleware hook that we have initialized previously
const { isDarkTheme, setIsDarkTheme } = useAppPersistStore();

return (
<Button
onPress={() => setIsDarkTheme(!isDarkTheme)}
title={isDarkTheme ? "Dark Theme" : "Light theme"}
/>
);
};

export default Home;

Listeners with react-native-mmkv

We can use React Native MMKV’s listener feature to track changes in the storage. This is useful to perform certain actions whenever a particular value in the storage changes.

Here is an example of how we can use the listener in a React Native application:

import * as React from "react";
import { Button } from "react-native";
import { MMKV } from "react-native-mmkv";

const storage = new MMKV({ id: "theme-preference" });

const Home: React.FC = () => {
const [isDarkTheme, setIsDarkTheme] = React.useState(false);

React.useEffect(() => {
const listener = storage.addOnValueChangedListener((changedKey) => {
const newValue = storage.getString(changedKey);

if (changedKey === "isDarkTheme") {
// update the theme in the app based on the updated value in the storage
console.log(`New theme preference: ${newValue}`);
}
});

return () => {
listener.remove();
};
}, []);

const toggleTheme = () => {
const newPreference = !isDarkTheme;
setIsDarkTheme(newPreference);
storage.set("isDarkTheme", JSON.stringify(newPreference));
};

return (
<Button
onPress={toggleTheme}
title={isDarkTheme ? "Dark Theme" : "Light Theme"}
/>
);
};

export default Home;

In this example, we have created a MMKV storage with the ID theme-preference and added a listener to it using the addOnValueChangedListener method. The listener listens for changes in the storage and updates the theme in the app accordingly.

The example above was intended to provide an illustration of the usage of the listener feature in React Native MMKV with a minimal amount of code, rather than being a meaningful example. In the code snippet, the listener is used to listen for changes in the storage and log the new values of the changed key.

Using with Expo

React native mmkv is indeed compatible with expo, however since it is built on top of native modules, it will not work in a typical expo app (which has no native code generated) out of the box. We need to generate native code, or in other words, we need to leverage prebuild feature of expo.

Limitations

As the library uses JSI for synchronous native methods access, remote debugging (e.g. with Chrome) is no longer possible. Instead, you should use Flipper.

Conclusion

As a react native developer, I highly recommend React Native MMKV as a storage solution for React Native applications. The library offers documentation on how to integrate it with other popular state management libraries such as jotai, redux-persist, mobx-persist, and zustand-persist-middleware, making it easy to incorporate into any project.

In comparison to React Native Async Storage, React Native MMKV offers several advantages but crucial advantages, including high performance, encryption support, and a user-friendly API. Its fully synchronous calls, with no need for async/await or Promises, make it a faster and more efficient choice.

Therefore, if you’re seeking a reliable and secure storage solution for your React Native app, I encourage you to consider using React Native MMKV.

Resources

Level Up Coding

Thanks for being a part of our community! Before you go:

🚀👉 Join the Level Up talent collective and find an amazing job

--

--