react-native-mmkv
to improve app performanceWhile working with React Native, you’ve most likely used AsyncStorage
as a storage solution. For example, you can use AsyncStorage
to store key-value pairs like the current theme of your application, or even to store states and tokens for various reasons.
Other than AsyncStorage
, there are some third-party storage solutions that we can use. In this article, we will look at the react-native-mmkv
library and explore why you might want to use it instead of AsyncStorage
and how to use it in our application. We will cover:
You can check out the code samples used in this article in this GitLab repository.
react-native-mmkv
?The react-native-mmkv
library developed by WeChat allows us to efficiently store and read key-value pairs from the MMKV storage framework. Its name is short for React Native memory-map key-value storage.
Similar to AsyncStorage
, react-native-mmkv
is cross-platform compatible, meaning that it works for both iOS and Android platforms. Let’s look at some of the reasons you might consider using MMKV over AsyncStorage
.
AsyncStorage
is an unencrypted storage system. It’s not advisable to use a storage solution like AsyncStorage
to store sensitive information like passwords, tokens, and other private information.
MMKV is more secure than the AsyncStorage
, offering data encryption and other more advanced security features. If you want to store sensitive data that requires a high level of security, MMKV is the better choice.
AsyncStorage
is an asynchronous storage system that makes use of async/await with promises. In comparison, with react-native-mmkv
, all calls are fully synchronous and can therefore be made without any promises.
Let’s look at the code below to understand better:
// AsyncStorage // storing data const storeUser = async (value) => { try { await AsynStorage.setItem("user", JSON.stringify(value)); } catch (error) { console.log(error); } }; storeUser("Chimezie") // getting data const getUser = async () => { try { const userData = await AsynStorage.getItem("user") const user = JSON.parse(userData) } catch (error) { console.log(error); } };
If you look at the code above, you can see that we used async/await for both storing and retrieving data from our storage.
With react-native-mmkv
, instead of making a promise request, we can call it as shown below:
// react-native-mmkv storage.set('username', 'Chimezie'); const username = storage.getString('username')
In the react-native-mmkv
example above, we are setting our username to our storage. When we want to retrieve the data, we use the getString()
method since we are getting string data. react-native-mmkv
doesn’t return a promise like AsyncStorage
does.
If you observe our example above, you will notice we are stringifying the value of the user we want to store in AsyncStorage
. This is because AsyncStorage
deals with string values, so we have to serialize all non-string data types before saving.
See the code below:
const storeUser = async () => { try { const userData = { name: "Chimezie", location: "Nigeria" } const serializedUser = JSON.stringify(userData) await AsynStorage.setItem("user", serializedUser); } catch (error) { console.log(error); } };
Similarly, to retrieve the data using AsyncStorage
, we have to parse the data to the original primitive — in this case, an object:
const getUser = async () => { try { const userData = await AsynStorage.getItem("user") const userObject = JSON.parse(userData) } catch (error) { console.log(error); } };
With MMKV, it is not so. MMKV is more efficient in this regard because it supports different primitives or data types like Boolean, numbers, and strings. Simply put, you don’t have to manually serialize all values before storing:
storage.set('username', 'Innocent') // string storage.set('age', 25) // number storage.set('is-mmkv-fast-asf', true) // boolean
Since react-native-mmkv
does not serialize and parse non-string data, it’s faster than AsyncStorage
. The image below from the MMKV team shows the results of a benchmark test of how long it takes for data to be read from different storage solutions a thousand times. MMKV proves to be faster than the rest:
Also, since MMKV is fully synchronous, it removes the strain of waiting for a promise to complete before getting the data. This makes it easier and faster to read and write data quickly — and without needing to handle any promise or error logic.
react-native-mmkv
Although the react-native-mmkv
library has many benefits, it also comes with certain limitations. In this section, we will be looking at some considerations to keep in mind while using MMKV.
MMKV utilizes JavaScript Interface (JSI), which provides a synchronous native access for efficient and improved performance. However, this poses a challenge for remote debugging as tools like Chrome DevTools use the traditional React Native bridge and not the JSI bridge.
As a workaround for this limitation, you can use the Flipper debugging tool. Flipper is designed for debugging when JSI is enabled in your app or when your app uses JSI libraries like MMKV. Alternatively, you can log your errors to the console for debugging purposes.
The MMKV library is very efficient for storing small amounts of data like user preferences, theme state, or application settings. No particular size measurement or limit is specified, but using MMKV for small data is recommended.
However, as it provides in-memory storage, storing large data will consume memory and impede the app’s performance. As a result, it is not recommended to use MMKV to store large amounts of data.
Unlike AsyncStorage
, the react-native-mmkv
library has limited documentation. The only available documentation is in the README.md
file in the library’s GitHub repo, which explains how to use the library.
react-native-mmkv
We have now seen some of the reasons you might consider using MMKV over AsyncStorage
, as well as some of its limitations. In this section, we will look at how to use the react-native-mmkv
package in our application to store and retrieve key-value pair data.
MMKV doesn’t work in Expo, so you can either use it in a bare React Native project or by prebuilding and ejecting your Expo app. To install the package, run any of the commands below:
// npm npm install react-native-mmkv //yarn yarn add react-native-mmkv
Next, we will create an instance and then initialize it. We can then call the instance anywhere in the application.
Create a file called Storage.js
and copy the code below:
// Storage.js import { MMKV } from 'react-native-mmkv' export const storage = new MMKV({ id: `user-storage`, path: `${USER_DIRECTORY}/storage`, encryptionKey: 'encryptionkey' })
In the code above, we first import the installed package. Next, we create an instance called storage
that can take three options — id
, path
, and encryptionKey
.
The id
is a unique identifier used to separate MMKV instances. You can create different instances to store different kinds of data. The id
helps to differentiate or separate them:
const passwordStorage = new MMKV({ id: `password-storage`, }) const themeStorage = new MMKV({ id: `theme-storage`, })
The path
is the root file in your device where MMKV stores the data. It also allows you to customize your preferred path or directory.
The encryptionKey
is a unique key used to encrypt and decrypt data before storing and after retrieving it.
After we have created our instance, we can then import the instance in any component and use it.
As we saw earlier, MMKV supports different data types, which means we can store different data types without serializing them.
First, let’s import the package we installed:
//App.js import { storage } from './Storage'
Next, we’ll use the storage to store our data:
storage.set('username', 'Innocent') // string storage.set('age', 25) // number storage.set('is-mmkv-fast-asf', true) // boolean
That’s it! As you can see in the code comments above, react-native-mmkv
supports string, number, and Boolean data types. However, for objects and arrays, we must first serialize the data before saving:
// objects const user = { name: "Chimezie", location: "Nigeria", email: '[email protected]', } storage.set("userDetails", JSON.stringify(user)) // arrays const numberArray = [1, 2, 3, 4, 5]; const serializedArray = JSON.stringify(numberArray); storage.set('numbers', serializedArray);
Retrieving data with MMKV is as easy and straightforward as storing it. You use getString()
for string data, getNumber()
for number data, and getBoolean()
for Boolean types:
const username = storage.getString('username') // 'Innocent' const age = storage.getNumber('age') // 25 const isMmkvFastAsf = storage.getBoolean('is-mmkv-fast-asf') // true
For objects and arrays, we will use getString()
because we serialized it — in other words, stored it as string data — before saving. Then, we will use JSON.parse()
to deserialize or convert back to the original state or data type:
// objects const serializedUser = storage.getString('userDetails'); const userObject = JSON.parse(serializedUser); console.log(userObject); /* output: const user = { name: "Chimezie", location: "Nigeria", email: '[email protected]', } */ // arrays const serializedArray = storage.getString('numbers'); const numberArray = JSON.parse(serializedArray); console.log(numberArray); // Output: [1, 2, 3, 4, 5]
Furthermore, in a scenario where you want to see all the keys in your storage, MMKV allows us to do this by providing a method called getAllKeys()
. This method returns an array of all the keys stored in an instance:
// Set some key-value pairs storage.set('name', 'Innocent'); storage.set('age', 25); const allKeys = storage.getAllKeys(); console.log(allKeys); // Output: ['name', 'age']
Deleting data with MMKV is similar to AsyncStorage
. We can delete a particular key or all the keys in our instance:
// delete a key storage.delete('username') // delete all keys storage.clearAll()
Data encryption is another area where MMKV shines over AsyncStorage
. MMKV has the option to encrypt your data before storing it, whereas AsyncStorage
does not offer encryption capabilities.
Before encrypting any data, we must first provide an encryption key in our instance. MMKV uses this key to encrypt and decrypt the data:
// Storage.js import { MMKV } from 'react-native-mmkv' export const storage = new MMKV({ id: `user-storage`, encryptionKey: 'EncrypTedKey123' })
Then, we can encrypt whatever data we want, like so:
// App.js storage.set('userPassword', 'This is a secret user password'); // Retrieving data from the encrypted storage const password = storage.getString('userPassword'); console.log(password); // Output: 'This is a secret user password'
react-native-mmkv
allows us to subscribe to updates, or changes to key-value pair data. To subscribe to updates, we can use the addOnValueChangedListener()
method to register an event listener. The listener is then notified whenever there’s a change in the specified key-value data.
Let’s see how we can do this:
// App.tsx import React, {useState, useEffect} from 'react'; import {Colors} from 'react-native/Libraries/NewAppScreen'; import { Text, View, Button, StatusBar, StyleSheet, SafeAreaView, } from 'react-native'; import {storage} from './Storage'; function App(): JSX.Element { const [isDarkMode, setIsDarkMode] = useState<boolean>(false); const backgroundStyle = { backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, }; useEffect(() => { const listener = storage.addOnValueChangedListener(changedKey => { if (changedKey === 'isDarkMode') { const newValue = storage.getBoolean(changedKey); console.log('theme:', newValue); } }); return () => { listener.remove(); }; }, []); const toggleTheme = () => { const newMode = !isDarkMode; setIsDarkMode(newMode); storage.set('isDarkMode', newMode); }; return ( <SafeAreaView style={[backgroundStyle, styles.sectionContainer]}> <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor={backgroundStyle.backgroundColor} /> <View> <Text style={[ styles.sectionTitle, { color: isDarkMode ? Colors.white : Colors.black, }, ]}> {isDarkMode ? 'Dark Mode' : 'Light Theme'} </Text> <Text style={[ styles.sectionDescription, { color: isDarkMode ? Colors.light : Colors.dark, }, ]}> React Native MMKV Tutorial </Text> <Button onPress={toggleTheme} title={isDarkMode ? 'Switch to light mode' : 'Switch to dark mode'} /> </View> </SafeAreaView> ); } const styles = StyleSheet.create({ sectionContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', }, sectionTitle: { fontSize: 24, fontWeight: '600', textAlign: 'center', }, sectionDescription: { marginVertical: 8, fontSize: 18, fontWeight: '400', textAlign: 'center', }, highlight: { fontWeight: '700', }, }); export default App;
In the code above, we use addOnValueChangedListener()
method to register a callback function. The function listens for changes to specific keys in the MMKV storage.
The callback function takes the changedKey
as an argument, fetches the new value associated with that key using storage.getBoolean(changedKey)
, and logs its new value to the console.
Whenever a key-value pair in MMKV is modified, the callback function will be invoked with the name of the changed key, allowing you to react to the changes in your application.
Finally, we are unsubscribing from the listener in our useEffect
return function. This is so as to avoid or prevent memory leaks whenever our component unmounts.
In this article, we looked at some of the reasons why you might consider using MMKV over AsyncStorage
and considered some of its limitations, too. You can see the code samples we used in this GitLab repo.
It’s true that MMKV is newer than AsyncStorage
and does not have a broad community like AsyncStorage
does. However, MMKV offers a much faster, more efficient, and more secure storage system. The library has over 50 contributors and remains very well-maintained.
We also explored how to use the MMKV storage system in a React Native app. I know you will fully master it in a short amount of time if you follow the article. If you have any questions, feel free to comment them below. All the best!
LogRocket is a React Native monitoring solution that helps you reproduce issues instantly, prioritize bugs, and understand performance in your React Native apps.
LogRocket also helps you increase conversion rates and product usage by showing you exactly how users are interacting with your app. LogRocket's product analytics features surface the reasons why users don't complete a particular flow or don't adopt a new feature.
Start proactively monitoring your React Native apps — try LogRocket for free.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.