---
title: "Creating a React Native Ecommerce app with Medusa"
description: "This is a new page"
url: https://medusajs.com/blog/creating-react-native-ecommerce-app-with-medusa
last_updated: 2026-08-13T14:48:17Z
---

# Creating a React Native Ecommerce app with Medusa

_Published 2022-06-09 by Suhail Kakar in Ecosystem_

> **Notice** Medusa is under active development, so the content of this post may be outdated. Please check out our documentation instead.
>
> [Go to Docs](http://docs.medusajs.com)

[React Native](https://reactnative.dev) is a cross-platform mobile app framework that allows you to build native apps for iOS and Android using JavaScript. It was developed by Meta Platforms, Inc. and it is currently among the most popular JavaScript frameworks for building native apps with a huge active community behind it.

[Medusa](https://github.com/medusajs/medusa) is an open source headless commerce platform that allows you to create online shopping stores in a few minutes. It includes every feature a store needs such as order management, customers, payments, products, discounts, and a lot more.

In this tutorial, you are building a React Native mobile commerce application with Medusa. For this part, you are going to create two screens, one for all products and the other for the product info.

You can also find the source code of the React Native app on the [**GitHub**](https://github.com/suhailkakar/react-native-medusajs/tree/part-1)

![Screen-Recording-2022-05-05-at-1](https://assets.medusajs.com/uploads/2024/06/6ec3ee294ccea638ef3bd22e8a3c362009bbfa07-1914x996-kf8b666c.gif)

## Prerequisites

Before you start with the tutorial make sure you have [Node.js](https://nodejs.org/en/) v14 or greater installed on your machine.

## Set Up Medusa Server

The first step is to setup the Medusa server, where the backend and APIs are handled.

You can install the Medusa CLI on your machine by running the following command:

```typescript
npm install -g @medusajs/medusa-cli
```

Once the CLI is installed successfully, run the following command to create a Medusa project:

```typescript
medusa new my-medusa-store --seed
```

The `--seed` option is used to add dummy data such as products and users to the store.

Change to the newly-created directory `my-medusa-store` and run the following command to start the medusa server:

```typescript
npm start
```

It is recommended to add a storage plugin to be able to add products with images in Medusa. You can use [**MinIO**](https://docs.medusajs.com/add-plugins/minio), [**AWS S3**](https://docs.medusajs.com/add-plugins/s3/), or [**Spaces**](https://docs.medusajs.com/add-plugins/spaces/).

## Set Up Medusa Admin

Medusa has a very powerful admin dashboard where you can manage your products, payments, transaction, and more. This is very easy to setup however it is optional, so if you want you can skip this section.

In a separate directory, clone the Medusa Admin:

```typescript
 git clone https://github.com/medusajs/admin medusa-admin
```

Once it is cloned, you should see a new directory named `medusa-admin`. Navigate to the new directory and run the following command to install the dependencies of the project:

```typescript
npm install
```

### Run Admin\*\* \*\*Panel

Finally, make sure that the Medusa server is still running and start the admin panel server by running the following command:

```typescript
npm run develop
```

Now, open your browser and navigate to `localhost:7000` and you should see the login page for admin panel. Login in to the admin with the below credentials.

- Email: [**admin@medusa-test.com**](mailto:admin@medusa-test.com)
- Password: supersecret

> The `--seed` option you used earlier adds an admin user with the email and password

### Create Products

Once you are logged in successfully, choose Products from sidebar and you should see the list of products in your store.

> If you are experiencing connection issues, it's most likely because of CORS issues. Check [here](https://docs.medusajs.com/troubleshooting/cors-issues) how to fix it.

![CNtujb5ve](https://assets.medusajs.com/uploads/2024/06/f30b1cbb38fa1d459b55846965852a092f3d8df4-2032x1092-xve6gmds.webp)

You can also create a new product by clicking on the "New Product" button. Add information for your product such as a name, description, handle, variants, images, prices, and a lot more.

![lDe6wdWg6](https://assets.medusajs.com/uploads/2024/06/ce4321435d4437c55ad4322d64ebedcbc967d83e-2032x1092-y1apd8a2.webp)

## Set Up React Native Ecommerce Project

Now that you have the store backend and admin panel ready it is time to start working on the react native ecommerce app.

In this tutorial, you are using Expo CLI to build the app. Run the following command to install the Expo CLI:

```typescript
npm install -g expo-cli
```

Once the CLI is installed successfully, run the following command to create a new react native ecommerce project:

```typescript
expo init
```

You will be promoted with some questions. You can follow the below code for the answers:

```typescript
What would you like to name your app? … medusa-store
Choose a template: › blank a minimal app as clean as an empty canvas
Downloaded template.

🧶 Using Yarn to install packages. Pass --npm to use npm instead.
Installed JavaScript dependencies.

✅ Your project is ready!

To run your project, navigate to the directory and run one of the following yarn commands.

- cd medusa-store
- yarn start # you can open iOS, Android, or web from here, or run them directly with the commands below.
- yarn android
- yarn ios
- yarn web
```

### Install Dependencies

Once the project is created successfully you should see a new directory named `medusa-store`. Navigate to the new directory and run the following command to install a few other dependencies:

```typescript
expo install react-native-screens react-native-router-flux react-native-reanimated rn-responsive-screen react-native-safe-area-context @expo/vector-icons react-native-gesture-handler axios
```

- `react-native-screens` is used to expose native navigation container components to React Native.
- `react-native-router-flux` provides API that helps users navigate between screens.
- `react-native-reanimated` creates smooth animations and interactions that run on the UI thread.
- `rn-responsive-screen` is a small package used for responsiveness in the app.
- `react-native-safe-area-context` is a flexible way to handle safe areas.
- `react-native-gesture-handler` provides native-driven gesture management APIs for building the best possible touch-based experiences.
- `axios` is a promise-based HTTP Client to easily send requests to REST APIs and perform CRUD operations.
- `@expo/vector-icons` includes popular icons sets which you can use in the app.

### Run Expo Development Server

After the packages are installed successfully, start the development server by running the following:

```typescript
expo start
```

You can either scan the QR code using your device or run the app on an Android/iOS simulator. Once the app is shown on your mobile, you should see a similar screen.

![T_VjUvvQY](https://assets.medusajs.com/uploads/2024/06/b9cd7893889203f24783d024d6f8ca7857d7cf9c-562x1059-a77jm9xo.webp)

This is a basic react native code in the `App.js` file.

### Set Up Routes

In this section, you’ll set up different routes in your app.

Before setting up the routes, you have to create a few screens. Create a new folder named `screens` and inside it create a new file named `Products.js`.

Inside `Products.js` insert the following code:

```typescript
import { StyleSheet, Text, View } from "react-native";

export default function Products() {
  return (
    <View style={styles.container}>
      <Text>Product Screen!</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
});
```

For now it contains a very simple `Text` component.

Now that you have a screen setup, you can continue to add routes to the project. Replace the code inside of the `App.js` with the following:

```typescript
import { Router, Scene, Stack } from "react-native-router-flux";
import Products from "./screens/Products";

export default function App() {
  return (
    <Router>
      <Stack key="root">
        <Scene key="products" component={Products} hideNavBar />
      </Stack>
    </Router>
  );
}
```

In the above code, you are using `react-native-router-flux` to create the navigation. `Router` is used as a parent component and each `Scene` represents one screen. For now you have just one screen.

Save the file and you might see an error similar to this.

```typescript
Error: Requiring module "node_modules/react-native-reanimated/src/Animated.js", which threw an exception: Error: Reanimated 2 failed to create a worklet, maybe you forgot to add Reanimated's babel plugin?
```

It is because that `react-native-router-flux` uses `react-native-reanimated` and in order to make it work you need to add it to `babel.config.js`. Open the babel file from your directory and add the below line after `presents`:

```typescript
plugins: ["react-native-reanimated/plugin"],
```

Save the file and restart the server with the following command:

```typescript
expo start -c
```

The option `-c` clears the cache before running the server.

> If you see the error “Invariant Violation: Tried to register two views with the same name RNGestureHandlerButton”, delete the `node_modules` directory and use [Yarn](https://yarnpkg.com/) to re-install the dependencies.

### Products List Screen

Create a new folder in the root directory named `components`. In the `components` folder create 3 files. `Button.js`, `ProductCard.js`, and `Header.js`.

In the `Button.js` file insert the following code to create a basic button component:

```typescript
import { View, Text, StyleSheet } from "react-native";
import React from "react";
import { widthToDp } from "rn-responsive-screen";

export default function Button({ title, onPress, style, textSize }) {
  return (
    <View style={[styles.container, style]}>
      <Text
        style={[styles.text, { fontSize: textSize ? textSize : widthToDp(3.5) }, ]}
        onPress={onPress}
      >
        {title}
      </Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    backgroundColor: "#C37AFF",
    padding: 5,
    width: widthToDp(20),
    alignItems: "center",
    justifyContent: "center",
    borderRadius: 59,
  },
  text: {
    color: "#fff",
    fontWeight: "bold",
  },
});
```

Similarly in the `Header.js` insert the following code to create a simple header component:

```typescript
import { View, Image, StyleSheet, Text } from "react-native";
import React from "react";

export default function Header({ title }) {
  return (
    <View style={styles.container}>
      <Image
        source={{
          uri: "https://user-images.githubusercontent.com/7554214/153162406-bf8fd16f-aa98-4604-b87b-e13ab4baf604.png",
        }}
        style={styles.logo}
      />
      <Text style={styles.title}>{title}</Text>
    </View>
  );
}
const styles = StyleSheet.create({
  container: {
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    marginBottom: 10,
  },
  title: {
    fontSize: 20,
    fontWeight: "500",
  },
  logo: {
    width: 50,
    height: 50,
  },
});
```

The last one is `ProductCard.js`. It is the main component in which you render the product data:

```typescript
import { View, Text, Image, StyleSheet } from "react-native";
import React from "react";
import { widthToDp, heightToDp } from "rn-responsive-screen";
import Button from "./Button";

export default function ProductCard({ key, product }) {
  return (
    <View style={styles.container} key={key}>
      <Image
        source={{
          uri: product.thumbnail,
        }}
        style={styles.image}
      />
      <Text style={styles.title}>{product.title}</Text>
      <Text style={styles.category}>{product.handle}</Text>
      <View style={styles.priceContainer}>
        <Text style={styles.price}>
          ${product.variants[0].prices[1].amount / 100}
        </Text>

        <Button
          title="BUY"
        />
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    shadowColor: "#000",
    borderRadius: 10,
    marginBottom: heightToDp(4),
    shadowOffset: {
      width: 2,
      height: 5,
    },
    shadowOpacity: 0.25,
    shadowRadius: 6.84,
    elevation: 5,
    padding: 10,
    width: widthToDp(42),
    backgroundColor: "#fff",
  },
  image: {
    height: heightToDp(40),
    borderRadius: 7,
    marginBottom: heightToDp(2),
  },
  title: {
    fontSize: widthToDp(3.7),
    fontWeight: "bold",
  },
  priceContainer: {
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
    marginTop: heightToDp(3),
  },
  category: {
    fontSize: widthToDp(3.4),
    color: "#828282",
    marginTop: 3,
  },
  price: {
    fontSize: widthToDp(4),
    fontWeight: "bold",
  },
});
```

> In the above code, the product price is divided by 100 because in Medusa the prices are hosted on the server without decimals.

### Create URL Constant

Create a new folder named `constants` and inside it create a new file named `url.js` with the following content:

```typescript
const baseURL = "http://127.0.0.1:9000";

export default baseURL;
```

In the above code, you define your Medusa server’s base URL. To be able to connect from your device to the local server, you must change the value of `baseURL` to your machine’s IP address. You can refer to [this guide](https://www.whatismybrowser.com/detect/what-is-my-local-ip-address) to learn how to find your IP address.

### Products Screen

That’s it for the components. Now replace the code in the `Products.js` with the following:

```typescript
import { ScrollView, StyleSheet,TouchableOpacity, View } from "react-native";
import React, { useEffect, useState } from "react";
import ProductCard from "../components/ProductCard";
import { widthToDp } from "rn-responsive-screen";
import axios from "axios";
import Header from "../components/Header";
import { Actions } from "react-native-router-flux";
import baseURL from "../constants/url";

export default function Products() {
  const [products, setProducts] = useState([]);

  function fetchProducts() {
        axios.get(`${baseURL}/store/products`).then((res) => {
      setProducts(res.data.products);
    }); 
 }

  useEffect(() => {
    fetchProducts();
  }, []);

  return (
    <View style={styles.container}>
      <Header title="Medusa's Store" />
      <ScrollView>
        <View style={styles.products}>
          {products.map((product) => (
            <ProductCard key={product.id} product={product} />
          ))}
        </View>
      </ScrollView>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    paddingTop: 50,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
  products: {
    flex: 1,
    flexDirection: "row",
    flexWrap: "wrap",
    width: widthToDp(100),
    paddingHorizontal: widthToDp(4),
    justifyContent: "space-between",
  },
});
```

In the code above, you call `fetchProducts` when the screen loads using `useEffect`. In the `fetchProducts` function, you use `axios` to fetch the [products from the Medusa server](https://docs.medusajs.com/api/store/product/list-products) and save it in the state.

Once you fetch the products, you render them using the `ProductCard` component.

### Run the App

Save the file and make sure that Expo and the Medusa server are running. Then, open the app on your device and you should see on the home screen the products from your Medusa server.

![Untitled](https://assets.medusajs.com/uploads/2024/06/81d3eae148ecfe963c8ec82cc9322dd7deb8f463-591x1115-dvkm7i71.webp)

### Product Info Screen

In this section, you’ll create the Product Info screen where the user can see more details about the product.

In the `screens` directory, create a new file named `ProductInfo.js` and for now you can use it to render a simple `Text` component:

```typescript
import { View, Text } from "react-native";
import React from "react";

export default function ProductInfo() {
  return (
    <View>
      <Text>Product Info Screen</Text>
    </View>
  );
}
```

Then, add the import `ProductInfo` at the top of `App.js`:

```typescript
import ProductInfo from "./screens/ProductInfo";
```

And add a new `Scene` component below the existing `Scene` component in the returned JSX:

```typescript
<Scene key="ProductInfo" component={ProductInfo} hideNavBar />
```

In the `components` directory, create a new directory named `ProductInfo` and create inside it `Image.js` with the following content:

```typescript
import { View, TouchableOpacity, Image, StyleSheet } from "react-native";
import React, { useEffect, useState } from "react";
import { widthToDp } from "rn-responsive-screen";

export default function Images({ images }) {
  const [activeImage, setActiveImage] = useState(null);

  useEffect(() => {
    setActiveImage(images[0].url);
  }, []);

  return (
    <View style={styles.imageContainer}>
      <Image source={{ uri: activeImage }} style={styles.image} />
      <View style={styles.previewContainer}>
        {images.map((image, index) => (
          <TouchableOpacity
            key={index}
            onPress={() => {
              setActiveImage(image.url);
            }}
          >
            <Image
              source={{ uri: image.url }}
              style={[
                styles.imagePreview,
                {
                  borderWidth: activeImage === image.url ? 3 : 0,
                },
              ]}
            />
          </TouchableOpacity>
        ))}
      </View>
    </View>
  );
}

const styles = StyleSheet.create({
  image: {
    width: widthToDp(100),
    height: widthToDp(100),
  },
  previewContainer: {
    flexDirection: "row",
    justifyContent: "center",
    alignItems: "center",
    marginTop: widthToDp(-10),
  },
  imageContainer: {
    backgroundColor: "#F7F6FB",
    paddingBottom: widthToDp(10),
  },
  imagePreview: {
    width: widthToDp(15),
    marginRight: widthToDp(5),
    borderColor: "#C37AFF",
    borderRadius: 10,
    height: widthToDp(15),
  },
});
```

In the above component you display a main big image and below it the rest of the product images as thumbnails. When the user press on one of the thumbnail images it is set as the active image and displayed as the main image.

In the `Products.js` file, replace the map function in the returned JSX with the following:

```typescript
{products.map((product) => (
    <TouchableOpacity key={product.id} onPress={() => Actions.ProductInfo({ productId: product.id })}>
      <ProductCard product={product} />
    </TouchableOpacity>
  ))
}
```

You add a `TouchableOpacity` that navigates the user to the product info screen when they click on a product.

> Using `react-router-flux` you pass the product ID from the home screen to the product info screen.

Then, replace the code in `ProductInfo.js` with the following:

```typescript
import { View, Text, ScrollView,TouchableOpacity, StyleSheet } from "react-native";
import React, { useState, useEffect } from "react";
import axios from "axios";
import { SafeAreaView } from "react-native-safe-area-context";
import Images from "../components/ProductInfo/Image";
import baseURL from "../constants/url";
import { Actions } from "react-native-router-flux";
import { Ionicons } from "@expo/vector-icons";

export default function ProductInfo({ productId }) {
  const [productInfo, setproductInfo] = useState(null);

    useEffect(() => {
    axios.get(`${baseURL}/store/products/${productId}`).then((res) => {
      setproductInfo(res.data.product);
    });
  }, []);

  return (
    <SafeAreaView style={styles.container}>
            <TouchableOpacity onPress={() => Actions.pop()}>
        <Ionicons
          style={styles.icon}
          name="arrow-back-outline"
          size={24}
          color="black"
        />
      </TouchableOpacity>
      <ScrollView>
        {productInfo && (
          <View>
            <Images images={productInfo.images} />
          </View>
        )}
      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
container: {
    flex: 1,
    backgroundColor: "#fff",
    justifyContent: "center",
  },
  icon: {
    marginLeft: 10,
  },
});
```

To briefly explain the code snippet:

- First you import all necessary components.
- Then you fetch the product data in useEffect function and save it in the state.
- Finally, you display the images using the `Images` component.

### Test Product Info Screen

Open the app now and click on any product on the home screen. A new screen will open showing the product’s images.

![NwGh1xt9_](https://assets.medusajs.com/uploads/2024/06/b03456ee5a9707d79f32ada8b0a22d25fff96dae-562x1059-bqcvgt2k.webp)

Now, you’ll display the product’s information.

In the `components` folder, inside the `ProductInfo` directory create a new file named `MetaInfo.js` with the following content:

```typescript
import { View, Text, StyleSheet } from "react-native";
import React, { useState } from "react";
import { height, heightToDp } from "rn-responsive-screen";

export default function MetaInfo({ product }) {
  const [activeSize, setActiveSize] = useState(0);
  return (
    <View style={styles.container}>
      <View style={styles.row}>
        <Text style={styles.title}>{product.title}</Text>
        <View>
          <Text style={styles.price}>
            ${product.variants[0].prices[1].amount / 100}
          </Text>
          <Text style={styles.star}>⭐⭐⭐</Text>
        </View>
      </View>
      <Text style={styles.heading}>Available Sizes</Text>
      <View style={styles.row}>
        {product.options[0].values.map((size, index) => (
          <Text
            onPress={() => {
              setActiveSize(index);
            }}
            style={[
              styles.sizeTag,
              {
                borderWidth: activeSize === index ? 3 : 0,
              },
            ]}
          >
            {size.value}
          </Text>
        ))}
      </View>
      <Text style={styles.heading}>Description</Text>
      <Text style={styles.description}>{product.description}</Text>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    marginTop: heightToDp(-5),
    backgroundColor: "#fff",
    borderTopLeftRadius: 20,
    borderTopRightRadius: 20,
    height: heightToDp(50),
    padding: heightToDp(5),
  },
  title: {
    fontSize: heightToDp(6),
    fontWeight: "bold",
  },
  row: {
    flexDirection: "row",
    justifyContent: "space-between",
    alignItems: "center",
  },
  price: {
    fontSize: heightToDp(5),
    fontWeight: "bold",
    color: "#C37AFF",
  },
  heading: {
    fontSize: heightToDp(5),
    marginTop: heightToDp(3),
  },
  star: {
    fontSize: heightToDp(3),
    marginTop: heightToDp(1),
  },
  sizeTag: {
    borderColor: "#C37AFF",
    backgroundColor: "#F7F6FB",
    color: "#000",
    paddingHorizontal: heightToDp(7),
    paddingVertical: heightToDp(2),
    borderRadius: heightToDp(2),
    marginTop: heightToDp(2),
    overflow: "hidden",
    fontSize: heightToDp(4),
    marginBottom: heightToDp(2),
  },
  description: {
    fontSize: heightToDp(4),
    color: "#aaa",
    marginTop: heightToDp(2),
  },
});
```

In the above component, you render the product title, price, description and variants.

For the product variant, you map all the variants and when a user press on one of them, you set that variant as active.

Save the `MetaInfo.js` file and import it at the top of `screens/ProductInfo.js`:

```typescript
import MetaInfo from "../components/ProductInfo/MetaInfo";
```

Then, in the returned JSX add the `MetaInfo` component below the `Images` component:

```typescript
<MetaInfo product={productInfo} />
```

### Test New Changes in Product Info Screen

Save the changes and check the app now. The product info screen now shows details about the product.

![Untitled%25201](https://assets.medusajs.com/uploads/2024/06/3f248f6e37995339cdfaa418591c6517a25afa94-591x1115-49hgl9e9.webp)

## **What’s Next?**

This article gives you the basis for creating a Medusa and React Native apps. Here are some more functionalities you can add using Medusa:

1. [Add a cart and allow adding products to cart.](https://docs.medusajs.com/api/store/cart/add-a-shipping-method)
2. Add a payment provider using [Stripe](https://docs.medusajs.com/guides/checkouts/#selecting-payment-provider).
3. Add a search engine using [MeiliSearch](https://docs.medusajs.com/add-plugins/meilisearch).
4. Check out the [documentation](https://docs.medusajs.com) for what more you can do with Medusa.

Should you have any issues or questions related to Medusa, then feel free to reach out to the Medusa team via [Discord](https://discord.gg/medusajs). Also, check out our [Next.js-based Storefront template](https://github.com/medusajs/nextjs-starter-medusa).
