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

Zustand: Warenkorb global in React Native verwalten

Zustand: Warenkorb global verwalten

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

Zustand löst BEIDE Probleme aus dem letzten Kapitel gleichzeitig: ein Store existiert AUSSERHALB des Komponentenbaums (echte Synchronisation, kein Duplikat-State mehr) UND Komponenten können sich über Selektoren GEZIELT nur auf die Werte abonnieren, die sie wirklich brauchen (kein unnötiges Re-Rendern).

Zustand installieren

npx expo install zustand

store/cartStore.js anlegen: mit Persist-Middleware

Anders als useCart, das AsyncStorage manuell in jeder Funktion aufruft, nutzen wir Zustands eingebaute persist-Middleware – sie übernimmt das Laden/Speichern automatisch im Hintergrund:

store/cartStore.js
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import AsyncStorage from '@react-native-async-storage/async-storage';

export const useCartStore = create(
  persist(
    (set, get) => ({
      cart: [],

      addProduct(product) {
        set({ cart: [...get().cart, product] });
      },

      removeProduct(sku) {
        set({ cart: get().cart.filter((item) => item.sku !== sku) });
      },
    }),
    {
      name: 'cart-storage', // Schlüssel, unter dem AsyncStorage die Daten ablegt
      storage: createJSONStorage(() => AsyncStorage),
    }
  )
);

get() (neben set das zweite Argument des Store-Erzeugers) liest den AKTUELLEN Store-Zustand von INNERHALB einer Store-Funktion – get().cart statt einer eingefangenen, möglicherweise veralteten cart-Variable aus einem Closure. persist(...) umschließt die Store-Definition und übernimmt Laden beim Start UND Speichern bei JEDER set()-Änderung automatisch – kein manuelles AsyncStorage.getItem/setItem mehr nötig.

Achtung: createJSONStorage(() => AsyncStorage) ist RN-spezifisch – Zustands persist-Middleware nutzt im Web standardmäßig localStorage (synchron), in React Native braucht sie eine ASYNCHRONE Speicher-Engine wie AsyncStorage, explizit über createJSONStorage angegeben. Genau diese Anpassung fehlte in "React für Profis" Kapitel 28 (Web), weil sie dort nicht nötig war.

ProductDetailScreen.js auf useCartStore umstellen

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

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

  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.button}
        onPress={() => addProduct(product)}
      >
        <Text style={styles.buttonText}>In den Warenkorb</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 16 },
  loader: { flex: 1, justifyContent: 'center' },
  error: { padding: 16, color: '#dc2626' },
  image: { width: '100%', height: 240, borderRadius: 12, marginBottom: 16 },
  name: { fontSize: 22, fontWeight: 'bold' },
  price: { fontSize: 18, color: '#2563eb', marginVertical: 8 },
  button: { backgroundColor: '#2563eb', padding: 14, borderRadius: 8, alignItems: 'center', marginTop: 16 },
  buttonText: { color: 'white', fontWeight: 'bold' },
});

export default ProductDetailScreen;

ProductListScreen.js auf useCartStore umstellen

// In screens/ProductListScreen.js, zwei Änderungen:

// 1. Import ersetzen:
import { useCartStore } from '../store/cartStore';

// 2. Innerhalb der Komponente, statt "const { cart } = useCart();":
const cart = useCartStore((state) => state.cart);

useCartStore((state) => state.cart) statt der kompletten Destrukturierung – ProductListScreen braucht NUR cart zum Anzeigen der Länge, nicht addProduct. Der Rest der Datei (Suche, FlatList, Header-Badge) bleibt UNVERÄNDERT wie in Kapitel 1 gezeigt.

Den Bug aus Kapitel 1 erneut testen

Wiederholen Sie die Schritte aus Kapitel 1: Produkt öffnen, "In den Warenkorb", zurück navigieren. Das Badge zeigt jetzt SOFORT den korrekten, aktualisierten Wert – BEIDE Screens lesen denselben, einzigen cart-Zustand aus demselben Store, keine unabhängigen Kopien mehr.

hooks/useCart.js entfernen

hooks/useCart.js wird von keiner Komponente mehr importiert – löschen Sie die Datei UND den (jetzt leeren) hooks/-Ordner. Der Store lebt ab jetzt vollständig in store/cartStore.js.

Tipp: createJSONStorage akzeptiert JEDE Speicher-Engine mit getItem/setItem/removeItem-Methoden – für sensible Daten (siehe das Passwort-Manager-Thema in der "React Native Referenz"-Serie) könnten Sie genauso expo-secure-store statt AsyncStorage einsetzen, mit identischer persist-Konfiguration.