AsyncStorage, TypeScript, and Async/await in React Native
In the last few posts, I’ve been working on a Notes app in a master-detail pattern for React Native using TypeScript. This is the last post about that project. So far, I’ve:
- Worked out how to handle orientation changes.
- Worked out how to implement swipe-to-delete.
- Figured out the best way to use TypeScript.
- Integrated MobX for the Flux pattern.
- Fixed up the project for universal iOS apps.
- Finally written the Master-Detail pattern.
That’s a lot of stuff. This last post is about storage. React Native provides a method of storing data on the device called AsyncStorage. It follows the Storage API that is fairly well known in the JavaScript world. It’s promise driven and generally backed by SQLite, which is a nice performant on-device storage that is common on both iOS (as Core Data) and Android.
Some housekeeping first. I wired up a new icon that looks like a plus sign to the following event handler in MasterDetail.tsx
:
This creates a new note and then sets it as active. Once the user enters some information, it will be stored in the store. Now, onto the AsyncStorage stuff. The best practice suggested is to wrap the AsyncStorage class with your own class. I can use the wrapping to implement some logic for storing the data locally. Here is the LocalStorage.ts
file:
This introduces a new concept for JavaScript: async and await. These are basically markers for a Promise. Marking a method as “async” says “this returns a Promise”. Since this is TypeScript, I’m specifying the return type anyway and it’s obvious it returns a Promise.
There is a flip side to this, which is to make the calling method use await, like this:
const note = await localStorage.getItem(noteId);
You can only use await inside of an async method, so the promise bubbles up to the top. I’ve got three items – getItem()
, deleteItem()
and saveItem()
to do the normal CRUD elements. I’ve also got a getAllItems()
that fetches all the notes from the store. I go to some lengths to ensure that only JSON objects for notes end up in the notes table, and I don’t deal with exceptions (I should do this!).
In my noteStore.ts
, I use this LocalStorage class like this:
Note that I don’t deal with the promise – it just stores the data asynchronously and I move about my day. If I were to do this “properly”, I would have a queue of data and a queue processor that was based on a service worker that processed the queue. This would prevent a race condition within the code where the app shuts down before the save to storage happens. However, the volume of data that is being stored is so low, I’m viewing this as a very low probability.
The store now stores the notes to local storage, but I don’t have anything to read the local storage on startup. I do have the initializeNotes()
method that cleans out the notes array and replaces it with another notes array. Note that I edit the array in-situ rather than creating a new array. I’m honestly not sure this is worth it in the observable world, but it can’t hurt anything. My notes initialization is done at the bottom of the notesStore.ts
file:
const observableNoteStore = new NoteStore();
localStorage
.getAllItems()
.then(items => observableNoteStore.initializeNotes(items));
This is a standard promise pattern. The getAllItems()
method resolves to the list of notes from the local storage, and I use that to populate the notes in my in-memory store.
That’s it for this series. Now I’m going to use this knowledge to produce a prettier version of the Notes app!
Leave a comment