Daten lokal speichern mit AsyncStorage
Daten lokal speichern mit AsyncStorage
~8 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
Wenn ein Nutzer die App schließt und neu öffnet, ist bisher der gesamte State weg – der Warenkorb ist leer, die Favoriten sind vergessen. Um Daten über App-Neustarts hinweg zu speichern, gibt es AsyncStorage.
Installation
npx expo install @react-native-async-storage/async-storageDer Vergleich zu localStorage
Wenn Sie localStorage aus dem Browser kennen, ist AsyncStorage konzeptionell fast identisch – Schlüssel-Wert-Speicher für Strings. Der große Unterschied: localStorage ist SYNCHRON (localStorage.setItem(...) blockiert kurz, bis fertig), AsyncStorage ist ASYNCHRON (gibt ein Promise zurück, das Sie mit await abwarten) – das ist nötig, weil der native Speicherzugriff auf iOS/Android nicht blockierend sein darf, sonst würde die ganze App-Oberfläche kurz einfrieren.
| HTML/Web | React Native |
|---|---|
localStorage.setItem('key', 'wert') | await AsyncStorage.setItem('key', 'wert') |
localStorage.getItem('key') | await AsyncStorage.getItem('key') |
localStorage.removeItem('key') | await AsyncStorage.removeItem('key') |
nur Strings, komplexe Daten via JSON.stringify() | identisch: nur Strings, komplexe Daten via JSON.stringify()/JSON.parse() |
hooks/useCart.js anlegen: ein eigener Hook
Legen Sie einen neuen Ordner hooks/ mit der Datei useCart.js an. Das ist ein sogenannter "Custom Hook" (Funktionsname beginnt mit use) – eine wiederverwendbare Funktion, die andere Hooks wie useState/useEffect kombiniert:
import { useState, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
const STORAGE_KEY = 'warenkorb';
export function useCart() {
const [cart, setCart] = useState([]);
// Beim Start: gespeicherten Warenkorb laden
useEffect(() => {
async function loadCart() {
const saved = await AsyncStorage.getItem(STORAGE_KEY);
if (saved) {
setCart(JSON.parse(saved));
}
}
loadCart();
}, []);
// Bei jeder Änderung: Warenkorb speichern
async function addProduct(product) {
const newCart = [...cart, product];
setCart(newCart);
await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newCart));
}
return { cart, addProduct };
}Jede Komponente, die useCart() aufruft, bekommt Zugriff auf denselben Muster-Code, ohne ihn zu duplizieren. [...cart, product] erzeugt ein NEUES Array mit allen alten Einträgen plus dem neuen – React-State darf niemals direkt verändert werden (z. B. cart.push(product) wäre falsch), sondern immer nur durch ein komplett neues Objekt/Array ersetzt werden. Das ist einer der häufigsten Anfängerfehler.
ProductDetailScreen.js um Warenkorb-Button erweitern
Jetzt verwenden wir useCart in unserem echten screens/ProductDetailScreen.js aus Kapitel 10. Ersetzen Sie den kompletten Inhalt der Datei durch diese Version:
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}>Produkt konnte nicht geladen werden.</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}>
In den Warenkorb ({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;Neu ist der Import von useCart, die Zeile const {{ cart, addProduct }} = useCart(); und der Button am Ende. Da useCart den Warenkorb selbst per AsyncStorage lädt und speichert, muss sich ProductDetailScreen um nichts weiter kümmern – der Warenkorb bleibt auch nach vollständigem Schließen der App erhalten.
Fertige Projektstruktur – genau die aus Kapitel 3 angekündigte
produktkatalog-app/ ├── App.js (Einstiegspunkt: Navigation) ├── app.json (Expo-Konfiguration) ├── package.json (Pakete, wie composer.json) ├── api/ │ └── magentoApi.js (Verbindung zur Magento-REST-API) ├── components/ │ └── ProductCard.js (wiederverwendbare Produktkarte) ├── hooks/ │ └── useCart.js (Warenkorb, dauerhaft gespeichert) ├── screens/ │ ├── ProductListScreen.js (Produktliste + Suche) │ └── ProductDetailScreen.js (Produktdetails + Warenkorb) └── assets/ (Icons, Bilder)
Damit ist unser Projekt komplett: eine echte, funktionierende App mit Produktliste, Live-Suche, Detailseite, Favoriten und einem dauerhaft gespeicherten Warenkorb – genau die Struktur, die wir uns in Kapitel 3 vorgenommen hatten. Die restlichen Kapitel drehen sich jetzt um Debugging, Tests auf echten Geräten und Veröffentlichung.
Achtung: AsyncStorage ist NICHT verschlüsselt und nicht für sensible Daten wie Passwörter oder Zahlungsinformationen gedacht. Dafür gibt es expo-secure-store, das die verschlüsselten System-Schlüsselspeicher von iOS (Keychain) und Android (Keystore) nutzt.