Magento 2 Experten — Hyvä Theme, Tailwind CSS & SEO aus einer Hand ›

Storing Data Locally with AsyncStorage

Storing Data Locally with AsyncStorage

~8 Min. Lesezeit Zuletzt aktualisiert am August 8, 2026

So far, when a user closes and reopens the app, all state is gone – the cart is empty, favorites are forgotten. To persist data across app restarts, there's AsyncStorage.

Installation

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

The comparison to localStorage

If you know localStorage from the browser, AsyncStorage is conceptually almost identical – a key-value store for strings. The big difference: localStorage is SYNCHRONOUS (localStorage.setItem(...) blocks briefly until done), AsyncStorage is ASYNCHRONOUS (returns a promise you await with await) – necessary because native storage access on iOS/Android must not block, or the entire app UI would briefly freeze.

HTML/WebReact Native
localStorage.setItem('key', 'value')await AsyncStorage.setItem('key', 'value')
localStorage.getItem('key')await AsyncStorage.getItem('key')
localStorage.removeItem('key')await AsyncStorage.removeItem('key')
strings only, complex data via JSON.stringify()identical: strings only, complex data via JSON.stringify()/JSON.parse()

Creating hooks/useCart.js: a custom hook

Create a new folder hooks/ with the file useCart.js inside it. This is what's called a "custom hook" (function name starts with use) – a reusable function that combines other hooks like useState/useEffect:

hooks/useCart.js
import { useState, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';

const STORAGE_KEY = 'cart';

export function useCart() {
  const [cart, setCart] = useState([]);

  // On startup: load the saved cart
  useEffect(() => {
    async function loadCart() {
      const saved = await AsyncStorage.getItem(STORAGE_KEY);
      if (saved) {
        setCart(JSON.parse(saved));
      }
    }
    loadCart();
  }, []);

  // On every change: save the cart
  async function addProduct(product) {
    const newCart = [...cart, product];
    setCart(newCart);
    await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newCart));
  }

  return { cart, addProduct };
}

Any component that calls useCart() gets access to the same pattern of code without duplicating it. [...cart, product] creates a NEW array with all the old entries plus the new one – React state must never be mutated directly (e.g. cart.push(product) would be wrong), only ever replaced with a brand new object/array. This is one of the most common beginner mistakes.

Extending ProductDetailScreen.js with a cart button

Now let's use useCart in our real screens/ProductDetailScreen.js from chapter 10. Replace the entire content of the file with this version:

screens/ProductDetailScreen.js
import { useState, useEffect } from 'react';
import {
  View,
  Text,
  Image,
  ActivityIndicator,
  TouchableOpacity,
  StyleSheet,
} from 'react-native';
import { fetchProductBySku } from '../api/magentoApi';
import { useCart } from '../hooks/useCart';

function ProductDetailScreen({ route }) {
  const { productSku } = route.params;
  const [product, setProduct] = useState(null);
  const [loading, setLoading] = useState(true);
  const { cart, addProduct } = useCart();

  useEffect(() => {
    async function loadProduct() {
      try {
        const data = await fetchProductBySku(productSku);
        setProduct(data);
      } catch (error) {
        console.error(error);
      } finally {
        setLoading(false);
      }
    }
    loadProduct();
  }, [productSku]);

  if (loading) {
    return <ActivityIndicator size="large" style={styles.loader} />;
  }

  if (!product) {
    return <Text style={styles.error}>Could not load product.</Text>;
  }

  return (
    <View style={styles.container}>
      <Image source={{ uri: product.imageUrl }} style={styles.image} />
      <Text style={styles.name}>{product.name}</Text>
      <Text style={styles.price}>${product.price.toFixed(2)}</Text>
      <TouchableOpacity style={styles.cartButton} onPress={() => addProduct(product)}>
        <Text style={styles.cartButtonText}>
          Add to Cart ({cart.length})
        </Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 16 },
  loader: { flex: 1, justifyContent: 'center' },
  error: { textAlign: 'center', marginTop: 24, color: '#ef4444' },
  image: { width: '100%', height: 220, borderRadius: 8, marginBottom: 16 },
  name: { fontSize: 22, fontWeight: 'bold', marginBottom: 8 },
  price: { fontSize: 18, color: '#6b7280', marginBottom: 20 },
  cartButton: {
    backgroundColor: '#111827',
    borderRadius: 8,
    paddingVertical: 14,
    alignItems: 'center',
  },
  cartButtonText: { color: '#fff', fontSize: 16, fontWeight: '600' },
});

export default ProductDetailScreen;

What's new is the useCart import, the line const {{ cart, addProduct }} = useCart();, and the button at the end. Since useCart loads and saves the cart itself via AsyncStorage, ProductDetailScreen doesn't need to worry about anything else – the cart survives even after fully closing the app.

Finished project structure – exactly what we announced in chapter 3

produktkatalog-app/
├── App.js                        (entry point: navigation)
├── app.json                      (Expo configuration)
├── package.json                  (packages, like composer.json)
├── api/
│   └── magentoApi.js             (connection to the Magento REST API)
├── components/
│   └── ProductCard.js            (reusable product card)
├── hooks/
│   └── useCart.js                (cart, persisted on the device)
├── screens/
│   ├── ProductListScreen.js      (product list + search)
│   └── ProductDetailScreen.js    (product details + cart)
└── assets/                       (icons, images)

That completes our project: a real, working app with a product list, live search, a detail page, favorites, and a persistently saved cart – exactly the structure we set out to build in chapter 3. The remaining chapters now turn to debugging, testing on real devices, and publishing.

Achtung: AsyncStorage is NOT encrypted and not meant for sensitive data like passwords or payment information. For that, use expo-secure-store, which relies on the encrypted system keystores of iOS (Keychain) and Android (Keystore).