How To Easily Persist Your Data With Context And AsyncStorage
Tags: JavaScript, React Native, Hooks, Persistence, React

How To Easily Persist Your Data With Context And AsyncStorage

Learn to use Hooks and AsyncStorage with React Native

cat in a box

It’s a fairly common practice to persist user data between sessions in most mobile apps and to get this done with React Native is as simple as using AsyncStorage. This will allow you to keep changes, or updates to any components even if the app is restarted, refreshed or closed.

Take this counter for example, if you increment or decrement the count and restart the app, the count will reset back to zero.

import React, { useContext, createContext, useState } from 'react';

import { View, Button, StyleSheet, Text } from 'react-native'; const CounterContext = createContext(0); const useCounter = () => useContext(CounterContext); const CounterContextProvider = ({ children }) => {

const [count, setCount] = useState(0);

const increment = () => setCount((value) => value + 1);

const decrement = () => setCount((value) => value - 1); return (

<CounterContext.Provider

value={{

count,

increment,

decrement

}}>

{children}

</CounterContext.Provider>

);

}; const App = () => {

const { count, increment, decrement } = useCounter(); return (

<View

style={styles.container}>

<Text>{count}</Text>

<Button title="Increment" onPress={() => increment()} />

<Button title="Decrement" onPress={() => decrement()} />

</View>

);

}; const styles = StyleSheet.create({

container: {

flex: 1,

alignItems: 'center',

justifyContent: 'center',

}

}); export default () => (

<CounterContextProvider>

<App />

</CounterContextProvider>

);

To fix that let’s implement AsyncStorage.

First store the count value in AsyncStorage with the useEffect hook and add the count as a dependency. By doing so, every time the count value is updated it will get stored.



import { View, Button, StyleSheet, Text } from 'react-native';

import AsyncStorage from ' import React, { useContext, createContext, useState, useEffect } from 'react';import { View, Button, StyleSheet, Text } from 'react-native';import AsyncStorage from ' @react -native-community/async-storage'; // ... const CounterContextProvider = ({ children }) => {

const [count, setCount] = useState(0);

const increment = () => setCount((value) => value + 1);

const decrement = () => setCount((value) => value - 1);



useEffect(() => {

AsyncStorage.setItem('COUNTER_APP::COUNT_VALUE', `${count}`);

}, [count]); return (

<CounterContext.Provider

value={{

count,

increment,

decrement

}}>

{children}

</CounterContext.Provider>

);

}; // ...

Don’t forget to convert the value to a string before storing it.

Now retrieve the state with the existing data from AsyncStorage.

const INITIAL_COUNT = 0; const CounterContextProvider = ({ children }) => {

const [count, setCount] = useState(INITIAL_COUNT);

const increment = () => setCount((value) => value + 1);

const decrement = () => setCount((value) => value - 1); useEffect(() => {

AsyncStorage.getItem('COUNTER_APP::COUNT_VALUE')

.then((value) = {

if (value) {

setCount(parseInt(value));

}

});

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

if (count !== INITIAL_COUNT) {

AsyncStorage.setItem('COUNTER_APP::COUNT_VALUE', `${count}`);

}

}, [count]); return (

<CounterContext.Provider

value={{

count,

increment,

decrement

}}>

{children}

</CounterContext.Provider>

);

}; // ...

Notice the new useEffect hook to retrieve data from AsyncStorage will only run on mount because it has no dependencies; it grabs the existing value and parses it as an integer before updating the count with that value.

Also the existing useEffect to store data in AsyncStorage now checks to make sure the stored value won’t be overridden by the initial value in the state.

This basic pattern can easily be scaled up to store more complex JSON objects.

Here is the final code for your reference.



import React, { useContext, createContext, useState, useEffect } from 'react';

import { View, TouchableOpacity, StyleSheet, Text } from 'react-native';

import AsyncStorage from ' // App.jsimport React, { useContext, createContext, useState, useEffect } from 'react';import { View, TouchableOpacity, StyleSheet, Text } from 'react-native';import AsyncStorage from ' @react -native-community/async-storage'; const CounterContext = createContext(0);

const useCounter = () => useContext(CounterContext);

const CounterContextProvider = ({ children }) => {

const [count, setCount] = useState(0); const increment = () => {

setCount((value) => value + 1);

}; const decrement = () => setCount((value) => value - 1);



useEffect(() => {

if (count !== 0) {

AsyncStorage.setItem('COUNTER_APP::COUNT_VALUE', `${count}`);

}

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

AsyncStorage.getItem('COUNTER_APP::COUNT_VALUE')

.then((value) => {

if (value) {

setCount(parseInt(value));

}

});

}, []);



return (

<CounterContext.Provider

value={{

count,

increment,

decrement,

}}>

{children}

</CounterContext.Provider>

);

}; const App = () => {

const { count, increment, decrement } = useCounter();

return (

<View style={styles.container}>

<Text style={styles.counterText}>{count}</Text>

<View style={styles.group}>

<TouchableOpacity

onPress={() => increment()}

style={styles.button}>

<Text style={styles.buttonTitle}>+</Text>

</TouchableOpacity>

<TouchableOpacity

onPress={() => decrement()}

style={styles.button}>

<Text style={styles.buttonTitle}>-</Text>

</TouchableOpacity>

</View>

</View>

);

}; const styles = StyleSheet.create({

container: {

flex: 1,

alignItems: 'center',

justifyContent: 'center',

},

button: {

backgroundColor: '#ff5f00',

width: 45,

justifyContent: 'center',

alignItems: 'center',

marginVertical: 8,

marginHorizontal: 8,

},

buttonTitle: {

fontSize: 34,

color: '#FFF',

},

counterText: {

fontSize: 24,

},

group: {

flexDirection: 'row',

},

}); export default () => (

<CounterContextProvider>

<App />

</CounterContextProvider>

);

Thank you and don’t forget to follow me on Twitter where you can comment or ask questions about this post.