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

Navigation zwischen Bildschirmen mit React Navigation

Navigation zwischen Bildschirmen

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

Bisher hatte unsere App nur einen einzigen Bildschirm. Ein echter Produktkatalog braucht mindestens eine Übersichtsliste und eine Detailseite. In HTML/Web würden Sie dafür verschiedene URLs und <a href>-Links verwenden. In React Native gibt es dafür kein eingebautes System – wir installieren die Standard-Bibliothek React Navigation.

Installation

Im Projektordner (mit dem Terminal in VS Code oder WebStorm):

npx expo install @react-navigation/native @react-navigation/native-stack
npx expo install react-native-screens react-native-safe-area-context

expo install statt npm install ist wichtig: Expo prüft dabei automatisch, welche Versionen zu Ihrer aktuellen Expo-SDK-Version passen – vergleichbar mit composer require, das automatisch mit den composer.json-Constraints kompatible Versionen wählt.

App.js wird unübersichtlich – Zeit zum Aufteilen

Unsere App.js enthält inzwischen Datenladen UND Anzeige in einer einzigen Datei. Genau jetzt, wo wir einen zweiten Bildschirm brauchen, ist der richtige Moment, den bisherigen Inhalt in eine eigene Datei screens/ProductListScreen.js auszulagern und App.js wieder schlank zu machen – nur noch für die Navigation zuständig.

magentoApi.js erweitern: ein einzelnes Produkt laden

Die Detailseite braucht eine Möglichkeit, EIN Produkt anhand seiner SKU zu laden. Erweitern Sie api/magentoApi.js um eine zweite exportierte Funktion (die bestehenden Zeilen bleiben unverändert, nur die neue Funktion kommt dazu):

api/magentoApi.js
const BASE_URL = 'https://mironsoft.test/rest/de/V1';

function mapMagentoProduct(item) {
  const imageAttribute = item.custom_attributes?.find(
    (attribute) => attribute.attribute_code === 'image'
  );

  return {
    sku: item.sku,
    name: item.name,
    price: item.price,
    imageUrl: imageAttribute
      ? `https://mironsoft.test/media/catalog/product${imageAttribute.value}`
      : 'https://picsum.photos/200',
  };
}

export async function fetchProducts() {
  const response = await fetch(`${BASE_URL}/products?searchCriteria[pageSize]=20`);

  if (!response.ok) {
    throw new Error(`Magento API error: ${response.status}`);
  }

  const data = await response.json();
  return data.items.map(mapMagentoProduct);
}

export async function fetchProductBySku(sku) {
  const response = await fetch(`${BASE_URL}/products/${encodeURIComponent(sku)}`);

  if (!response.ok) {
    throw new Error(`Magento API error: ${response.status}`);
  }

  const item = await response.json();
  return mapMagentoProduct(item);
}

Magento identifiziert Produkte über die REST-API standardmäßig per SKU, nicht per numerischer ID – deshalb heißt unser Identifikator überall sku, nicht id. encodeURIComponent() ist wichtig, falls eine SKU Sonderzeichen wie Leerzeichen oder Schrägstriche enthält.

Tipp: Nutzen Sie Variante B (dummyjson.com) aus Kapitel 8? Dann fügen Sie exakt dieselbe fetchProductBySku()-Funktion hinzu – dummyjson.com/products/{{id}} funktioniert genau wie Magentos Einzelprodukt-Endpunkt, nur BASE_URL und mapMagentoProduct() bleiben die aus Kapitel 8.

screens/ProductListScreen.js anlegen

Legen Sie einen neuen Ordner screens/ mit der Datei ProductListScreen.js an. Der Inhalt ist fast identisch mit dem, was gerade noch in App.js stand – nur der Funktionsname ändert sich, und onPress navigiert jetzt statt nur zu loggen:

screens/ProductListScreen.js
import { useState, useEffect } from 'react';
import { View, Text, FlatList, StyleSheet, ActivityIndicator } from 'react-native';
import { fetchProducts } from '../api/magentoApi';
import ProductCard from '../components/ProductCard';

function ProductListScreen({ navigation }) {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function loadProducts() {
      try {
        const items = await fetchProducts();
        setProducts(items);
      } catch (error) {
        console.error(error);
      } finally {
        setLoading(false);
      }
    }
    loadProducts();
  }, []);

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

  return (
    <View style={styles.container}>
      <FlatList
        data={products}
        keyExtractor={(item) => item.sku}
        renderItem={({ item }) => (
          <ProductCard
            name={item.name}
            price={item.price}
            imageUrl={item.imageUrl}
            onPress={() =>
              navigation.navigate('ProductDetail', { productSku: item.sku })
            }
          />
        )}
        ListEmptyComponent={<Text>Keine Produkte gefunden.</Text>}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, paddingTop: 16, paddingHorizontal: 16 },
  loader: { flex: 1, justifyContent: 'center' },
});

export default ProductListScreen;

Beachten Sie die Importe: '../api/magentoApi' und '../components/ProductCard' statt './...' – da diese Datei jetzt eine Ebene tiefer im Ordner screens/ liegt, muss sie mit ../ erst einen Ordner nach oben gehen, um api/ und components/ zu erreichen.

screens/ProductDetailScreen.js anlegen: unser zweiter Bildschirm

Das ist eine komplett neue Datei – unser erster echter zweiter Bildschirm. Er lädt sein eigenes Produkt anhand der über die Navigation übergebenen SKU:

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

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

  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>
    </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' },
});

export default ProductDetailScreen;

route.params enthält genau das Objekt, das ProductListScreen beim navigation.navigate(...)-Aufruf übergeben hat – hier {{ productSku }}. [productSku] als zweites useEffect-Argument (statt eines leeren Arrays) sorgt dafür, dass beim Navigieren zu einem ANDEREN Produkt erneut geladen wird.

App.js aufräumen: nur noch Navigation

Jetzt ersetzen wir App.js ein letztes Mal – sie enthält ab jetzt keine Datenlogik mehr, sondern nur noch die Navigation zwischen unseren beiden Bildschirmen:

App.js
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import ProductListScreen from './screens/ProductListScreen';
import ProductDetailScreen from './screens/ProductDetailScreen';

const Stack = createNativeStackNavigator();

export default function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="ProductList"
          component={ProductListScreen}
          options={{ title: 'Produktkatalog' }}
        />
        <Stack.Screen
          name="ProductDetail"
          component={ProductDetailScreen}
          options={{ title: 'Produktdetails' }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

Projektstruktur nach diesem Kapitel

produktkatalog-app/
├── App.js                          (aufgeräumt: nur noch Navigation)
├── app.json
├── package.json
├── api/
│   └── magentoApi.js                (erweitert: fetchProductBySku)
├── components/
│   └── ProductCard.js
├── screens/
│   ├── ProductListScreen.js         ← NEU (Inhalt aus altem App.js)
│   └── ProductDetailScreen.js       ← NEU
└── assets/

Starten Sie neu und tippen Sie auf ein Produkt: Sie navigieren zur Detailseite, mit einem nativen Zurück-Pfeil oben links. Zeile für Zeile im Vergleich zum Web:

HTML/WebReact Native
<a href="/produkt/wanderschuhe-42">navigation.navigate('ProductDetail', {{ productSku: 'wanderschuhe-42' }})
URL-Parameter (/produkt/:sku, $_GET['sku'])route.params im Ziel-Screen
Browser-Zurück-Buttonautomatischer "<"-Zurück-Pfeil oben links (nativ von iOS/Android) + Wisch-Geste auf iOS
Mehrere .html-Dateien bzw. Router-Routenmehrere Screen-Komponenten, registriert im Stack.Navigator

Tab-Navigator für die Hauptnavigation

Für eine Bottom-Tab-Leiste (Start, Suche, Warenkorb, Konto – wie in den meisten Shopping-Apps) gibt es @react-navigation/bottom-tabs, das sich genauso wie der Stack-Navigator einrichten lässt und sich sogar mit ihm kombinieren lässt (ein Stack pro Tab).

Tipp: Die offizielle Dokumentation auf reactnavigation.org ist ungewöhnlich gut und enthält für fast jeden Anwendungsfall ein kopierbares Beispiel – ein guter erster Anlaufpunkt, wenn Sie später eigene Navigationsstrukturen bauen.