Command Palette

Search for a command to run...

React Native Local Data Storage: How to use AsyncStorage & SecureStore

React Native Local Data Storage

In the previous article, our application learned how to "talk" to the Server via API to fetch data. However, a major issue arises:

Every time users close the app and reopen it, everything goes back to square one! Users have to log in again from scratch, the application forgets whether they are using Dark Mode or Light Mode, and the articles they have read are not saved.

If the API is the door connecting to the outside world, then Local Storage is the secret "notebook" hidden inside the phone. In this lesson, we will learn how to write in that notebook so your application has an excellent "memory"!

1. AsyncStorage: The "golden" standard of React Native

If you have ever done Web development, you definitely know about localStorage. In the React Native world, its cousin is named AsyncStorage.

AsyncStorage React Native

AsyncStorage is a data storage system in the form of Key - Value. All data you save here is converted into text strings (String) and will exist permanently on the phone until the user uninstalls the app or manually clears the app data.

Installing AsyncStorage in Expo

Open your project's Terminal and run the following command:

npx expo install @react-native-async-storage/async-storage

How to work with AsyncStorage

Because the phone's storage system needs time to read/write to the hard drive, all operations with AsyncStorage are Asynchronous. Therefore, you always have to use async / await when working with it.

Let's see how the application saves the user's display name:

import AsyncStorage from '@react-native-async-storage/async-storage'

// 1. SAVE DATA (Put in the closet)
const saveUsername = async (name) => {
  try {
    await AsyncStorage.setItem('@username_key', name)
    console.log('Successfully saved name!')
  } catch (e) {
    console.error('Error saving data:', e)
  }
}

// 2. GET DATA (Open closet and take out)
const getUsername = async () => {
  try {
    const value = await AsyncStorage.getItem('@username_key')
    if (value !== null) {
      console.log('Welcome back:', value)
    }
  } catch (e) {
    console.error('Error reading data:', e)
  }
}

// 3. REMOVE DATA (Logout)
const removeUsername = async () => {
  try {
    await AsyncStorage.removeItem('@username_key')
    console.log('Successfully removed data!')
  } catch (e) {
    console.error('Error removing data:', e)
  }
}

Note: AsyncStorage can ONLY store strings. If you want to store an Object containing user information (e.g.: { id: 1, role: 'admin' }), you must use JSON.stringify() before saving, and JSON.parse() after retrieving.

2. Absolute safety with Expo SecureStore

AsyncStorage is great for storing UI configurations (Theme, Language) or non-critical data.

But WARNING: You absolutely MUST NOT use AsyncStorage to store passwords, credit card information, or login Tokens (JWT)! Data in AsyncStorage is plain-text, hackers can easily plug a cable into the phone and extract all this data.

For sensitive information, the Expo ecosystem provides us with an "armored safe" named SecureStore. It encrypts your data using the highest-level security algorithms of the operating system (Keychain on iOS and Keystore on Android).

Expo SecureStore

Installation:

npx expo install expo-secure-store

Usage (Almost identical to AsyncStorage but 100 times safer):

import * as SecureStore from 'expo-secure-store'

// Save login Token securely
async function saveToken(token) {
  await SecureStore.setItemAsync('user_token', token)
}

// Retrieve Token to attach to API Request
async function getToken() {
  let result = await SecureStore.getItemAsync('user_token')
  if (result) {
    console.log('Your Password/Token is securely protected!')
  }
}

3. MMKV: The performance "monster" (For large projects)

As your application grows, you might realize AsyncStorage operates a bit slowly when having to save/read a list of thousands of viewed products.

The React Native community is currently going crazy over a new storage solution named react-native-mmkv (developed by Tencent).

MMKV React Native

Why is MMKV highly sought after?

  1. 30 times faster than AsyncStorage.
  2. It operates Synchronously! Meaning you don't need to write the tiring async/await anymore. The UI will display data immediately without any delay (Loading).

Note for Expo users: To use MMKV, you cannot use the standard Expo Go app, but must create a Development Build because it contains Native C++ source code that deeply intervenes in the memory. This is advanced knowledge that we will explore in the final stages of the series!

4. Practice: Onboarding Feature

Let's apply AsyncStorage right away to a very realistic scenario: When users just downloaded the app, you want to show the Onboarding screen. But from the 2nd time opening the app onwards, you want them to go straight to the Home Screen.

Let's use the useEffect Hook combined with AsyncStorage:

import React, { useEffect, useState } from 'react'
import { View, Text, ActivityIndicator } from 'react-native'
import AsyncStorage from '@react-native-async-storage/async-storage'
import { useRouter } from 'expo-router'

export default function RootApp() {
  const [isFirstLaunch, setIsFirstLaunch] = useState(null)
  const router = useRouter()

  useEffect(() => {
    async function checkFirstLaunch() {
      // Check if the 'alreadyLaunched' flag exists
      const hasLaunched = await AsyncStorage.getItem('alreadyLaunched')

      if (hasLaunched === null) {
        // Doesn't exist -> First app launch
        await AsyncStorage.setItem('alreadyLaunched', 'true')
        setIsFirstLaunch(true)
      } else {
        // Exists -> Not the first time
        setIsFirstLaunch(false)
      }
    }
    checkFirstLaunch()
  }, [])

  // Checking data in hard drive, showing spinner
  if (isFirstLaunch === null) {
    return <ActivityIndicator size="large" style={{ flex: 1 }} />
  }

  // Navigation based on result
  if (isFirstLaunch) {
    // If first time, router.replace() to Onboarding screen
    return <Text>Welcome newcomer! Redirecting to Onboarding...</Text>
  } else {
    // Not first time, go straight to Home Screen
    return <Text>Welcome back! Navigating to Home Screen...</Text>
  }
}

Conclusion: Local data is also very important

The memory notebook in React Native has 3 distinct compartments:

  1. AsyncStorage: Used for normal data, app configuration. (Easiest to install, most commonly used).
  2. SecureStore: The security safe. Only used for Passwords, JWT Tokens, payment data.
  3. MMKV: The jet engine. Used when you need massive data retrieval speeds in the blink of an eye.

Now, your application has the ability to remember and call APIs. But what if you fetch User data on the Profile page, and want to use that data again on the Settings page? Passing data back and forth between dozens of screens will create an extremely tangled mess (called Prop Drilling).

It's time we explore centralized state management solutions, see you in the next article!

Related Posts

A Guide to Requesting Permissions in React Native

A detailed guide on how to request Permissions on mobile devices with React Native & Expo. Master the use of expo-location and Linking to handle denials.

React Hooks Review: How They Work in a Mobile Environment

Not fully understanding data flow in React Native? This article will explain in detail how to use useState, useEffect, and useRef through the easiest-to-understand practical examples.

Stack Navigation in Expo Router: Navigating & Passing Parameters

A detailed guide on how to navigate and pass parameters using Stack Navigation in React Native, using Expo Router.

What is React Native? Why use it for Mobile programming?

Discover the power of React Native and the Expo platform that helps you build mobile apps quickly without the hassle of complex setups.