React Native Async Storage

React Native Async Storage

ยท

5 min read

๐Ÿ“ฑ Introduction

When you create an App, you'll most likely need to store and manipulate data for it, and React Native Async Storage makes it really easy to include such functionality.

It works almost the same as the browser's local storage. The data is stored as a string (a JSON.stringified object), and the API offers a (short) list of methods to perform any CRUD operations.

Since the data is unencrypted, you probably don't want to save sensitive data like passwords or anything auth-related. A typical use case would rather be the user's highscores in a game app, or their progress in a habit/exercise tracker app.

The default maximum size of the storage on Android is 6 MB, but it can be increased by adding one line to the android/gradle.properties:

AsyncStorage_db_size_in_MB=10

๐Ÿ“ฑ Basic usage

At the most basic level, you only need three methods to create/read/delete data. Operations on the storage are asynchronous, you can use async/await syntax in a try/catch block.

Installation:

npm i @react-native-async-storage/async-storage

Importing:

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

Create/save

const data = {key1: 'value-1', key2: 'value-2'};
const dataString = JSON.stringify(data);

AsyncStorage.setItem('my-storage-key', dataString);

Read

AsyncStorage.getItem('my-storage-key');

Delete

To delete the whole storage (not advised, because some of the packages you're using in your app might depend on the storage as well):

AsyncStorage.clear()

To delete the data for a specific key:

AsyncStorage.removeItem('my-storage-key');

๐Ÿ“ฑ Usage in a real React Native Application

I'll take my current project as an example: I recently discovered yoga (on a sidenote, it's awesome, your programmer's back will be most grateful), and now I'm building an app to keep track of my progress. Below is a screenshot of the current UI (the stats button leads to a different view to visually represent the progress, the seed and clear buttons are only used during development):

yoga-app-screenshot.png

It's the kind of yoga where you try to hold a certain position for as long as possible, which I measure in number of breaths. I've also noticed strong differences between left and right side of the body, so I keep separate records for those. The entry for one day would thus be a key/value pair with the date being the key, and the value being an array of objects with the following structure:

"2021/07/19": [ {exercise: "Eagle", "left": 12, "right": 20}, { ... }, { ... } ]

In the first iteration, I had stored everything under one key "progress", but decided to keep separate entries for each day instead. As the data grows, I'd otherwise have to rewrite the whole data as one value each time, instead of just adding another key/value pair. It also makes it easier to implement the possibility to update/delete an entry.


Creating the data with multiSet

For development, I've implemented two functions to seed the storage with a few entries, and to delete the whole storage. multiSet, as the name suggests, allows to set multiple entries at once, and accepts an array of arrays [key, value].

  const seedData = [
    {
      date: '2021/07/12',
      exercises: [
        { title: 'Eagle', left: '6', right: '12' },
        { title: 'Warrior III', left: '5', right: '7' },
        { title: 'Side Plank', left: '10', right: '7' },
      ],
    },
    {
      date: '2021/07/13',
      exercises: [
        { title: 'Eagle', left: '7', right: '12' },
        { title: 'Warrior III', left: '7', right: '7' },
        { title: 'Side Plank', left: '13', right: '9' },
      ],
    },
    {
      date: '2021/07/17',
      exercises: [
        { title: 'Eagle', left: '10', right: '20' },
        { title: 'Warrior III', left: '9', right: '9' },
        { title: 'Side Plank', left: '16', right: '9' },
      ],
    },
  ];

  const seedStorage = () => {

    // prepare the data for multiSet
    const dataArr = seedData.map((item) => [
      item.date,
      JSON.stringify(item.exercises),
    ]);

    const storeData = async (arr) => {
      try {
        await AsyncStorage.multiSet(arr);
        getData(); // see below
      } catch (err) {
        console.warn(`ERROR in seedStorage: ${err}`);
      }
    };

    storeData(dataArr);
  };

(That data is real, btw. You can either conclude that I'm in terrible shape, or that I'm making awesome progress.)


Deleting all data with multiRemove

The multiRemove method accepts an array of keys. Those can be retrieved with .getAllKeys (make sure that the only stored entries are from your code, and not something that one of your packages might depend on).

   const removeEntries = async () => {
    const keys = await AsyncStorage.getAllKeys();

    try {
      await AsyncStorage.multiRemove(keys);
      getData(); // see below
    } catch (err) {
      console.warn(`ERROR in removeEntries: ${err}`);
    }
  };

Reading the data with multiGet

To retrieve the data when the app loads, I first need to get all the stored keys with .getAllKeys again. The multiGet method accepts an array of keys and returns an array of arrays [key, value]:

const [ data, setData ] = useState(null);

const getData = async () => {

  try {
    const keys = await AsyncStorage.getAllKeys();

    if (keys.length > 0) {

      const storageJSON = await AsyncStorage.multiGet(keys);
      const storageData = storageJSON.map((item) => [
        item[0],
        JSON.parse(item[1]),
      ]);
      setData(storageData);

    } else {
      setData(null);
    }
  } catch (err) {
    console.warn(`ERROR in getData: ${err}`);
  }
};

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

This function also runs after every storage update, for example after deletion of all entries, hence the else statement to make sure that the UI reflects the current state of the storage.


Adding an entry with setItem

In the below code, I've left out the pure React parts that deal with getting the data from my input fields and creating the new date string and entry object.

When submitting a new entry, I first check if there's already an entry for that day in storage, the rest is completely straightforward:

  const storeData = async (date, entry) => {
    const keys = await AsyncStorage.getAllKeys();

    if (keys.includes(date)) {
      console.warn('This day already has an entry');
      return;
    }

    try {
      await AsyncStorage.setItem(date, entry);
      getData();
    } catch (err) {
      console.warn(`ERROR in storeData: ${err}`);
    }
  };

Editing and deleting an entry

Since the data is stored in JSON.stringified format, you can't update the object directly in storage, but overwrite it with setItem. Delete an entry with removeItem (examples for both methods above).


๐Ÿ“ฑ Resources

React Native Async Storage API


๐Ÿ“ฑ Thanks for reading!

If you find any errors or have additions, questions or just want to say hi, please leave a comment below, or get in touch via my website jsdisco.dev or Twitter.
Keep calm & code ๐Ÿ‘‹