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

Error Handling and Loading States

Error Handling and Loading States

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

So far, our app has silently assumed EVERY Magento request succeeds – in practice, an expired token, a wrong SKU, or a briefly unreachable shop happens sooner or later.

The three most common Magento API error cases

Error caseCause & handling
401 UnauthorizedThe access token is invalid or (with chapter 3's admin token) expired. Fix: get a new token, or switch to chapter 5's non-expiring integration token.
404 Not FoundThe requested SKU doesn't (or no longer) exist – e.g. because a product was deleted in the admin while a user still had the old link open.
Network error (no HTTP status)The shop is currently unreachable (maintenance mode, network issue) – fetch() itself throws an error in this case, BEFORE a response.status even exists.

magentoFetch() with differentiated error handling

src/api/magentoApi.js
// ... BASE_URL, ACCESS_TOKEN, PAGE_SIZE ...

export class MagentoApiError extends Error {
  constructor(message, status) {
    super(message);
    this.name = 'MagentoApiError';
    this.status = status;
  }
}

async function magentoFetch(path) {
  let response;
  try {
    response = await fetch(`${BASE_URL}${path}`, {
      headers: { Authorization: `Bearer ${ACCESS_TOKEN}` },
    });
  } catch {
    throw new MagentoApiError('Shop unreachable. Check your internet connection.', 0);
  }

  if (response.status === 401) {
    throw new MagentoApiError('Access expired or invalid. Please renew the token.', 401);
  }
  if (response.status === 404) {
    throw new MagentoApiError('Product not found.', 404);
  }
  if (!response.ok) {
    throw new MagentoApiError(`Unexpected error: ${response.status}`, response.status);
  }

  return response.json();
}

The custom MagentoApiError class additionally carries the HTTP status – so the calling component can distinguish, via instanceof/error.status, WHICH message to show, instead of displaying the same generic text for every error.

ProductDetailPage.jsx with error and loading state

src/pages/ProductDetailPage.jsx
import { useEffect, useState } from 'react';
import { useParams, Link } from 'react-router-dom';
import {
  fetchProductBySku,
  readCustomAttribute,
  getProductImage,
  getStockInfo,
} from '../api/magentoApi';

function ProductDetailPage() {
  const { sku } = useParams();
  const [product, setProduct] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    setError(null);
    async function load() {
      try {
        const data = await fetchProductBySku(sku);
        setProduct(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    }
    load();
  }, [sku]);

  if (loading) {
    return <p>Loading product...</p>;
  }

  if (error) {
    return (
      <div>
        <p>⚠ {error}</p>
        <Link to="/">Back to product list</Link>
      </div>
    );
  }

  const description = readCustomAttribute(product, 'description');
  const imageUrl = getProductImage(product);
  const { inStock, quantity } = getStockInfo(product);

  return (
    <div>
      {imageUrl && <img src={imageUrl} alt={product.name} width="300" />}
      <h1>{product.name}</h1>
      <p>SKU: {product.sku}</p>
      <p>{product.price} €</p>
      <p>{inStock ? `In stock (${quantity} available)` : 'Out of stock'}</p>
      {description && <div dangerouslySetInnerHTML={{ __html: description }} />}
    </div>
  );
}

export default ProductDetailPage;

The try/catch/finally pattern ensures loading ends up false in EVERY case (success OR error) – without finally, the app would get stuck in the loading state forever on an error.

Test it on purpose: provoke an error

Tipp: Manually change the URL to a SKU that doesn't exist (e.g. /products/DOES-NOT-EXIST) – you should now see the 404 error message with a way back, instead of a crashing blank page. Also try setting a wrong VITE_MAGENTO_ACCESS_TOKEN in .env to see the 401 case (restart the dev server afterward).