Async/Await und die Magento-API in React
Async/Await und die Magento-Anbindung
~13 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
Jetzt verbinden wir ProductListPage und ProductDetailPage endlich mit einer echten API, statt mit dem hart codierten SAMPLE_PRODUCTS-Array. Dafür lernen Sie async/await kennen – die lesbarere Schreibweise für .then()-Ketten.
async/await: dasselbe wie .then(), anders geschrieben
// Mit .then()-Ketten:
fetch(url)
.then((response) => response.json())
.then((data) => console.log(data));
// Mit async/await – dieselbe Logik, linear lesbar:
async function ladeDaten() {
const response = await fetch(url);
const data = await response.json();
console.log(data);
}await "pausiert" die Funktion an dieser Stelle, bis das Promise erfüllt ist – ohne den Rest der App zu blockieren. await ist nur innerhalb einer mit async markierten Funktion erlaubt.
Achtung: Wichtig, bevor Sie weitermachen: Der Code unten braucht eine ECHTE, erreichbare API im Hintergrund – https://mironsoft.test ist die lokale Docker-Adresse DIESES Projekts und funktioniert bei Ihnen nicht. Haben Sie einen eigenen Magento-2-Shop mit aktivierter REST-API? Dann tragen Sie unten dessen Adresse ein. Haben Sie (noch) keinen? Dann überspringen Sie den Magento-Block und nutzen direkt die kostenlose Alternative weiter unten – der Rest dieses Kapitels bleibt in beiden Fällen komplett identisch, da nur diese eine Datei die Datenquelle kennt.
Variante A: eigener Magento-Shop
Legen Sie einen neuen Ordner src/api/ mit der Datei magentoApi.js an:
const BASE_URL = 'https://mironsoft.test/rest/de/V1'; // ← Ihre eigene Shop-URL hier eintragen
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/300',
};
}
export async function fetchProducts() {
const response = await fetch(`${BASE_URL}/products?searchCriteria[pageSize]=20`);
if (!response.ok) {
throw new Error(`Magento API Fehler: ${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 Fehler: ${response.status}`);
}
const item = await response.json();
return mapMagentoProduct(item);
}Variante B: kein eigener Shop? Kostenlose Test-API ohne Anmeldung
dummyjson.com ist eine kostenlose, öffentliche Test-API – kein Account, kein API-Key nötig. Legen Sie dieselbe Datei stattdessen so an (die exportierten Funktionsnamen bleiben absichtlich identisch zu Variante A):
const BASE_URL = 'https://dummyjson.com';
function mapMagentoProduct(item) {
return {
sku: String(item.id),
name: item.title,
price: item.price,
imageUrl: item.thumbnail,
};
}
export async function fetchProducts() {
const response = await fetch(`${BASE_URL}/products?limit=20`);
if (!response.ok) {
throw new Error(`API Fehler: ${response.status}`);
}
const data = await response.json();
return data.products.map(mapMagentoProduct);
}
export async function fetchProductBySku(sku) {
const response = await fetch(`${BASE_URL}/products/${encodeURIComponent(sku)}`);
if (!response.ok) {
throw new Error(`API Fehler: ${response.status}`);
}
const item = await response.json();
return mapMagentoProduct(item);
}Beide Varianten exportieren exakt dieselben zwei Funktionen mit demselben Rückgabeformat – der Rest der App ruft nur fetchProducts()/fetchProductBySku() auf und weiß nicht, woher die Daten wirklich kommen.
ProductListPage.jsx: echte Daten statt SAMPLE_PRODUCTS
import { useEffect, useMemo, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import ProductCard from '../components/ProductCard';
import { fetchProducts } from '../api/magentoApi';
function ProductListPage() {
const [produkte, setProdukte] = useState([]);
const [ladend, setLadend] = useState(true);
const [query, setQuery] = useState('');
const searchInputRef = useRef(null);
const navigate = useNavigate();
useEffect(() => {
async function laden() {
try {
const items = await fetchProducts();
setProdukte(items);
} catch (error) {
console.error(error);
} finally {
setLadend(false);
}
}
laden();
}, []);
const gefiltert = produkte.filter((produkt) =>
produkt.name.toLowerCase().includes(query.toLowerCase())
);
const teuerstesProdukt = useMemo(() => {
return gefiltert.reduce(
(max, p) => (p.price > (max?.price ?? 0) ? p : max),
null
);
}, [gefiltert]);
if (ladend) {
return <p>Lade Produkte...</p>;
}
return (
<div>
<input
ref={searchInputRef}
type="text"
placeholder="Produkt suchen..."
value={query}
onChange={(event) => setQuery(event.target.value)}
/>
{teuerstesProdukt && (
<p>Teuerstes Produkt: {teuerstesProdukt.name} ({teuerstesProdukt.price.toFixed(2)} €)</p>
)}
<div className="product-grid">
{gefiltert.map((produkt) => (
<ProductCard
key={produkt.sku}
name={produkt.name}
price={produkt.price}
imageUrl={produkt.imageUrl}
onSelect={() => navigate(`/products/${produkt.sku}`)}
/>
))}
</div>
</div>
);
}
export default ProductListPage;Beachten Sie: searchInputRef.current.focus() aus Kapitel 9 fehlt hier jetzt bewusst – solange ladend true ist, existiert das Eingabefeld noch gar nicht im DOM. Wir kümmern uns im nächsten Kapitel (Error Handling und Loading States) genauer darum, wie sich Ladezustände sauber mit anderen Effekten kombinieren lassen.
ProductDetailPage.jsx: einzelnes Produkt laden
import { useEffect, useState } from 'react';
import { Link, Outlet, useParams } from 'react-router-dom';
import { fetchProductBySku } from '../api/magentoApi';
function ProductDetailPage() {
const { sku } = useParams();
const [produkt, setProdukt] = useState(null);
const [ladend, setLadend] = useState(true);
useEffect(() => {
async function laden() {
try {
const data = await fetchProductBySku(sku);
setProdukt(data);
} catch (error) {
console.error(error);
} finally {
setLadend(false);
}
}
laden();
}, [sku]);
if (ladend) {
return <p>Lade Produkt...</p>;
}
if (!produkt) {
return <p>Produkt nicht gefunden.</p>;
}
return (
<div>
<img src={produkt.imageUrl} alt={produkt.name} />
<h2>{produkt.name}</h2>
<p>{produkt.price.toFixed(2)} €</p>
<Link to={`/products/${sku}/reviews`}>Bewertungen ansehen</Link>
<Outlet />
</div>
);
}
export default ProductDetailPage;[sku] als Abhängigkeit sorgt dafür, dass beim Navigieren zu einem ANDEREN Produkt erneut geladen wird – genau wie im React-Native-Tutorial.
Projektstruktur nach diesem Kapitel (gekürzt)
produktkatalog-web/
└── src/
├── api/
│ └── magentoApi.js ← NEU
└── pages/
├── ProductListPage.jsx (aktualisiert: lädt echte Daten)
└── ProductDetailPage.jsx (aktualisiert: lädt echte Daten)