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

Fetching Data from an API with fetch and useEffect

Fetching Data from an API

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

So far we've hard-coded product data directly in our code. Real apps load data from a server – exactly like our Magento shop does via its REST or GraphQL API. This chapter shows how.

You already know fetch()

Good news first: React Native uses the exact same fetch() function you know from browser JavaScript. There's no difference from the web here:

fetch('https://myshop.example.com/rest/en/V1/products?searchCriteria[pageSize]=10')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error(error));

The problem: WHEN should it load?

Simply calling fetch() in the middle of the component function would trigger a new network call on every redraw ("re-render", e.g. caused by a useState change) – an infinite loop. That's what useEffect is for: a "side effect" that only runs at specific points in time.

A dedicated file for the product API connection

Achtung: Important before you continue: the code below needs a REAL, reachable API behind it – https://mironsoft.test is this project's local Docker address and won't work for you. Do you have your own Magento 2 shop with the REST API enabled? Then enter its address below. Don't have one (yet)? Then skip the Magento block and use the free alternative further down instead – the rest of the tutorial (App.js, ProductListScreen.js, etc.) stays completely identical either way, since only this one file knows where the data comes from.

Instead of scattering fetch() calls throughout the code, we bundle every API request in one central place. Create a new folder api/ with the file magentoApi.js inside it.

Option A: your own Magento shop

Replace https://mironsoft.test with your own shop's base URL:

api/magentoApi.js
const BASE_URL = 'https://mironsoft.test/rest/en/V1'; // ← enter your own shop URL here

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);
}

Option B: no shop of your own? A free test API, no signup

dummyjson.com is a free, public test API with sample products – no account, no API key needed. Create the same file like this instead (the exported function names are deliberately identical to option A):

api/magentoApi.js
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 error: ${response.status}`);
  }

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

Both options export the exact same fetchProducts() function with the same return shape (sku, name, price, imageUrl). That's the whole point of this dedicated file: the rest of the app (App.js and later screens/) only ever calls fetchProducts() and has no idea where the data actually comes from. From here on, chapter examples only show option A (Magento) – if you're using option B, apply the coming changes to api/magentoApi.js the same way conceptually.

Magento's REST API returns every product as a large, nested object with many fields we don't need at all (SKU, attribute sets, website IDs, ...). mapMagentoProduct() extracts exactly the four fields our ProductCard expects – the image lives as one of many "custom attributes" on the product and has to be found first. fetchProducts() is async – meaning the function immediately returns a promise, and the actual value only arrives later, once the network response comes back. That's exactly what await is for: it "pauses" the function until the promise resolves, without blocking the rest of the app.

Updating App.js: loading products on startup

Now let's replace App.js completely again – this time it loads real data from your shop instead of showing a fixed ProductCard. The ProductCard import comes back in the next chapter, once we display a whole list:

App.js
import { useState, useEffect } from 'react';
import { View, Text, StyleSheet, ActivityIndicator } from 'react-native';
import { fetchProducts } from './api/magentoApi';

export default function App() {
  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();
  }, []); // empty array = run only ONCE, on first render

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

  return (
    <View style={styles.container}>
      <Text>{products.length} products loaded</Text>
    </View>
  );
}

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

Project structure after this chapter

produktkatalog-app/
├── App.js                     (updated: loads products via useEffect)
├── app.json
├── package.json
├── api/
│   └── magentoApi.js          ← NEW
├── components/
│   └── ProductCard.js
└── assets/

The empty array [] as the second argument to useEffect is crucial – it tells React: "run this code only once, right after the component is first displayed", comparable to the DOMContentLoaded event on the web, or $(document).ready() in older jQuery code. Restart the app (or reload it from the developer menu) – you should briefly see the loading indicator, then the number of products loaded from your shop.

HTML/WebReact Native
window.addEventListener('DOMContentLoaded', load)useEffect(() => { load(); }, [])
Show/hide a loading spinner via a CSS classloading state + conditional rendering (if (loading) return ...)
fetch()identical – fetch() is built into React Native, no import needed

Tipp: For a public endpoint like /rest/V1/products without login you don't need an access token – for protected actions like orders you'd need an integration token, created under System > Integrations in the Magento backend.