AsyncStorage in React Native and how to use it with app state manager
AsyncStorage is React Native’s API for storing data persistently over the device. It’s a storage system that developers can use and save data in the form of key-value pairs. If you are coming from a web developer background, it resembles a lot like localStorage browser API.
AsyncStorage API is asynchronous, so each of its methods returns a Promise object and in case of error, an Error object. It’s also global, so use it with caution.
Why use AsyncStorage?
AsyncStorage can prove very helpful when we want to save data that the application would need to use, even when the user has closed it or has even closed the device. It is not a replacement for state data and it should not be confused with that; besides, state data will be erased from memory when the app closes.
A typical example of that, is the data that the app will need to login the user, like session id and/or user id. The app will need these data to be saved permanently over the device.
It is recommended that you use an abstraction on top of AsyncStorage instead of AsyncStorage directly
Simple usage
Importing the AsyncStorage library:
import { AsyncStorage } from "react-native"
Here, it suggests not to use AsyncStorage object directly, but instead use the API methods that are designed for this purpose — exactly as we are supposed to do with React’s class components and the state object / methods.
The basic actions to perform in AsyncStorage are:
- set an item, along with its value
- retrieve an item’s value
- remove an item
Save to AsyncStorage
Let’s set a new key called userId along with its value:
const userId = '8ba790f3-5acd-4a08-bc6a-97a36c124f29';
const saveUserId = async userId => {
try {
await AsyncStorage.setItem('userId', userId);
} catch (error) {
// Error retrieving data
console.log(error.message);
}
};
Simple as that we save GUID value to userId
key with the use of async/await
promise API (or .then
if you prefer).
Retrieve value from AsyncStorage
If we want to retrieve the value from previous example we do it like that:
const getUserId = async () => {
let userId = '';
try {
userId = await AsyncStorage.getItem('userId') || 'none';
} catch (error) {
// Error retrieving data
console.log(error.message);
}
return userId;
}
In this case, we only need the string key to refer to the AsyncStorage needed item. In case userId
key does not exist in AsyncStorage
(i.e. the first time the app loads), the function will return undefined
or in the example above the string 'none'
.
Delete from AsyncStorage
If we want to completely delete the reference key and its value set in previous example (i.e. we do a major change in our app and our login process changes) we do it like that:
const deleteUserId = async () => {
try {
await AsyncStorage.removeItem('userId');
} catch (error) {
// Error retrieving data
console.log(error.message);
}
}
Usage with state manager
When we use a state manager within our apps (i.e. Redux — or new React’s context API), it is a really good idea to abstract the related AsyncStorage code within the state manager code, instead of “overloading” the screen’s component code.
To understand what this means and how to achieve that, let’s assume our example from before; saving user’s id along with user’s session id this time. User and session id should be saved from the app during the registration process.
Example with Redux
Assuming that we have Redux configured along with a User
reducer, and a SAVE_USER
action, then the app will dispatch this action during user registration to save the new user data within the state. So instead of writing the extra code within the Register
component, we can do that inside the User
reducer.
In app/reducers/user.js
we will have the following code:
// packages
import {AsyncStorage} from 'react-native';
const initialState = {
id: null,
sessionId: null,
username: null,
password: null
};
export default (state = initialState, action) => {
switch (action.type) {
case 'SAVE_USER':
// save sessionId & userId in AsyncStorage
if (action.user.sessionId) {
AsyncStorage.setItem('sessionId', action.user.sessionId);
}
if (action.user.id) {
AsyncStorage.setItem('userId', action.user.id);
}
return {
...state,
id: action.user.id || state.id,
sessionId: action.user.sessionId || state.sessionId,
username: action.user.username || state.username,
password: action.user.password || state.password
});
default:
return state;
}
};
Check closer inside the reducer and you will see that we use the reducer’s abstraction to encapsulate inside it the invocation of AsyncStorage setItem
method.
And to dispatch the SAVE_USER
action, we invoke its mapped prop inside Register component like that:
this.props.saveUser();