top of page

How to build a mobile eCommerce app using React Native

Build your client's e-commerce application functionality with React Native and Wix Stores

Design by Jean Lorenzo

Profile picture of Roy Sommer

7.31.2024

9 min read

Building a mobile app today is easier than ever. 


With tools like React Native and Expo, the go-to code frameworks for building mobile apps with React & Javascript, it doesn’t take long to get one up and running.


Since there are many benefits of creating an app for clients, there’s a ton of demand for a fully customized experience. All you need is a Wix Studio site and some basic React knowledge to get started; you don't need prior experience with React Native. Using Wix Headless, which lets you use APIs to access and manage business data in Wix Studio's platform, you can build a mobile app to accompany your clients’ website.


(Find everything we make in this post in our Github repository.)



An example of a React Native e-commerce application


Set up with the right tools 


We are going to use two main tools for our endeavor:


  1. Expo, the leading React Native framework

  2. An existing Wix Studio site with Wix Stores installed


Before we start, let's prepare our work environment:


  1. We're going to write some React code. Make sure that you have an up to date version of NodeJS and your favorite IDE fired up and ready to go.

  2. You're going to test your app live on your mobile device, so install the Expo GO app on your phone.



Now lets build a mobile app with React Native


Note: In case you're already familiar with Expo, you can clone our "Getting Started" template from Github and head right over to the "Creating Product component" step.

Alright, now it's time to get started.


Every story starts with a good npm install. In our case, we're going to create an Expo app using the following command:


npx create-expo-app

Expo is the go-to framework for React Native application development, and we're going to use it to build our app with ease.

Running the command will prompt us to bootstrap a new React Native expo project, which we're going to name before installing the dependencies:



Development npx create-expo-app, What is your app named?

Once our app is ready, you can go ahead and open it in your IDE of choosing. You can use whichever app you feel most comfortable with, for instance, VS Code:


App example open in IDE

Let's review our project structure:


  1. The app directory is where we can find our pages. Similar to how nextjs works, the name and paths of the files determine the various routes in our app.

  2. The components directory is for reusable components.

  3. The hooks directory is for reusable hooks, which is useful in larger projects.

  4. The assets and constants directories are for common media files and hardcoded values to be used across the app.


Before we change anything, let's get our first confirmation that everything is working as expected, by running it on our mobile device. Run the following command:


npm start

And you'll be presented with a QRCode:



IDE example with QR code

Scan the QR Code you got (on your own computer, not the one shown here) with your mobile device. It should open app the Expo Go app, and show the default boilerplate app:



Expo Go App welcome screen

In this tutorial, we're going to create a single page application, so we can make the default router project structure a tad more simple: start by deleting the app/(tabs) directory, the app/layout.tsx, app/+html.tsx and app/+404.tsx files, and all files under components (but keep the directory). We do this since every new Expo app comes with a little bit of boilerplate code, which is very useful in case you’re building a large application, but in our case is redundant. (If you’re an experienced developer who’s creating a multi-page application, go ahead and skip this step.)


Instead, create a file called app/index.tsx with the following code:



import { SafeAreaView, Text } from 'react-native';

export default function HomePage() {
  return (
    <SafeAreaView>
      <Text>Coming soon...</Text>
    </SafeAreaView>
  );
}


Now we have a single page, with a <SafeAreaView /> component. This component is used to display content that never goes behind the status bar.


Our app should automatically refresh and look like this:


Coming soon...

Now we're ready to go. Let's create our storefront UI.



Creating a product component


Before connecting to our site, we're going to create the UI for our product catalog. This is a deep dive for those of you who are React experts but haven't tried React Native before; we're going to learn a little bit about structuring a React Native app.




Note: This section introduces React Native's building blocks & primitives...If you're already a React Native expert, you can skip this step and clone our "UI Ready" template from Github.


We'll start by creating the <ProductItem /> component, which we'll later use to show our products:


Matcha Powder listed

Create a new file under the components directory called ProductItem.tsx.


We're going to first define the component's props. Take a look at the expected result: we need four variables to perfectly show our product. These would be the title, the image, the description, and the price:



export interface ProductItemProps {
  title: string;
  description: string;
  price: string;
  image: string;
}


Now that we have our props ready, let's structure our component:



export const ProductItem = ({
  title,
  description,
  price,
  image,
}: ProductItemProps) => {
  return (
    <View>
      <Image source={{ uri: image }} />
      <View>
        <Text>{title}</Text>
        <Text>{description}</Text>
        <Text>{price}</Text>
      </View>
    </View>
  );
};


Let's go over our code. We use three main components: <View />, <Text /> and <Image />.


These components are React Native primitives, each with their own role:


  1. The <View /> component is used for layouts and containers, like a <div /> in HTML.

  2. The <Text /> component is used for displaying text, like a <span /> or <p /> elements would in HTML.

  3. The <Image /> component works similarly to the <img /> element in HTML. The only difference between them is that its source attribute expects an object and not a simple string.


Next, let's quickly add styles. In React Native, we don't use CSS files. Instead, we create a Stylesheet object in our code. The syntax is very similar to CSS, so this stylesheet object should look familiar:



const styles = StyleSheet.create({
  container: {
    flexDirection: 'column',
    flex: 1,
    gap: 8,
  },
  image: {
    width: '100%',
    height: 112,
    objectFit: 'cover',
  },
  infoContainer: {
    flexDirection: 'column',
    gap: 5,
  },
  titleLabel: {
    fontWeight: 'bold',
    fontSize: 16,
    color: '#20303C',
  },
  descriptionLabel: {
    fontWeight: '400',
    fontSize: 14,
    color: '#6E7881',
  },
  priceLabel: {
    fontWeight: '400',
    fontSize: 14,
    color: '#4D5963',
  },
});


Eventually, we get (including the import section) a ProductItem.tsx file that looks like this:



import { View, Text, Image, StyleSheet } from 'react-native';

export interface ProductItemProps {
  title: string;
  description: string;
  price: string;
  image: string;
}

export const ProductItem = ({
  title,
  description,
  price,
  image,
}: ProductItemProps) => {
  return (
    <View style={styles.container}>
      <Image style={styles.image} source={{ uri: image }} />
      <View style={styles.infoContainer}>
        <Text style={styles.titleLabel}>{title}</Text>
        <Text style={styles.descriptionLabel}>{description}</Text>
        <Text style={styles.priceLabel}>{price}</Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flexDirection: 'column',
    flex: 1,
    gap: 8,
  },
  image: {
    width: '100%',
    height: 112,
    objectFit: 'cover',
  },
  infoContainer: {
    flexDirection: 'column',
    gap: 5,
  },
  titleLabel: {
    fontWeight: 'bold',
    fontSize: 16,
    color: '#20303C',
  },
  descriptionLabel: {
    fontWeight: '400',
    fontSize: 14,
    color: '#6E7881',
  },
  priceLabel: {
    fontWeight: '400',
    fontSize: 14,
    color: '#4D5963',
  },
});


We can now test it out in our app, by refactoring the app/index.tsx file to show this component with sample data:



import { SafeAreaView } from 'react-native';
import { ProductItem } from '@/components/ProductItem';
export default function HomePage() {
  return (
    <SafeAreaView>
      <ProductItem
        title="Cookie Box"
        description="A box of chocolate chip cookies for you to enjoy!"
        price="25.00$"
        image="https://images.unsplash.com/photo-1612845575953-f4b1e3d63160?w=600"
      />
    </SafeAreaView>
  );
}


Congratulations! We now have a stylish <ProductItem /> component alive and kicking in our app.



Cookie Box item in product


Implementing the "Products Catalog"


Most product catalogs display your merchandise in a neatly arranged grid. We're going to create such a component, called <ProductsList /> , using React Native's own <FlatList /> component.


First off, let's create a new file under the components directory called components/ProductsList.tsx.


Like with our <ProductItem /> component, we start by defining our props. This component should be as simple as possible, and just accept an array of ProductItemProps:


import { ProductItemProps } from './ProductItem';
export interface ProductsListProps {
  products: ProductItemProps[];
}

Now, import the <FlatList /> component from react-native, and configure it as following in our code:



export const ProductsList = ({ products }: ProductsListProps) => {
  return (
    <FlatList
      data={products}
      numColumns={2}
      ListHeaderComponent={<Text>Products</Text>}
      renderItem={({ index, item }) => <ProductItem key={index} {...item} />}
    />
  );
};


<FlatList /> is an extremely useful component, not only because it helps you visualize a repeated component as a list, but also because it performs optimizations when the user scrolls and displays only the elements that need displaying.


Let's explain the props we passed here:


  1. The data prop is the array we want to base our list on. In our case, it's the products prop that has the list of products.

  2. numColumns represents how many items we want per line. Since we want this to be a grid with two items per row, we chose 2.

  3. ListHeaderComponent contains the title for the list.

  4. renderItem expects a function that gets an item and returns a component. We use the <ProductItem /> component we previously created.


Now that we have our list, let's give it a glow up with some styling. Feel free to copy our styles, or use your own:



const styles = StyleSheet.create({
  header: {
    color: '#9E79FD',
    fontWeight: 'bold',
    fontSize: 36,
    marginBottom: 10,
  },
  list: {
    height: '100%',
    paddingLeft: 10,
    paddingRight: 10,
  },
  column: {
    gap: 10,
    marginBottom: 10,
  },
});


Putting everything together, our component file now looks like this:



import { FlatList, Text, StyleSheet } from 'react-native';
import { ProductItem, ProductItemProps } from './ProductItem';

export interface ProductsListProps {
  products: ProductItemProps[];
}

export const ProductsList = ({ products }: ProductsListProps) => {
  return (
    <FlatList
      data={products}
      numColumns={2}
      columnWrapperStyle={styles.column}
      style={styles.list}
      ListHeaderComponent={<Text style={styles.header}>Products</Text>}
      renderItem={({ index, item }) => <ProductItem key={index} {...item} />}
    />
  );
};

const styles = StyleSheet.create({
  header: {
    color: '#9E79FD',
    fontWeight: 'bold',
    fontSize: 36,
    marginBottom: 10,
  },
  list: {
    height: '100%',
    paddingLeft: 10,
    paddingRight: 10,
  },
  column: {
    gap: 10,
    marginBottom: 10,
  },
});


Finally, let's update the component in app/index.tsx to use our product list:



import { SafeAreaView } from 'react-native';
import { ProductItemProps } from '@/components/ProductItem';
import { ProductsList } from '@/components/ProductsList';

export default function HomePage() {
  return (
    <SafeAreaView>
      <ProductsList products={fakeProducts} />
    </SafeAreaView>
  );
}


Look how simple the component now looks. If you want some fake data for show, add the following at the bottom of the file:



const fakeProducts: ProductItemProps[] = [
  {
    title: 'Empty!',
    description: 'Here is a kitty instead',
    price: '50.00$',
    image: 'https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba?w=600',
  }
];


And there you have it:




Empty set of products CMS. Default background is a cat


Now it's time to finally connect it to our site.



Make your site "Headless ready"


We'll first start by creating an OAuth app for our site. An OAuth app lets us connect to our site from outside of Wix on behalf of a visitor. (OAuth app also supports member login, but we're not going to cover this today.)


Head over to your site's backoffice (through "My Sites"). From there, select "Settings":



Settings tab in Wix Studio dashboard


And then "Headless Settings":



Settings tab in Wix Studio dashboard, headless settings to select


Now, click "Create OAuth App" and name your app:



Create OAuth App page in Wix Studio dashboard


Save your app. Now you should see it under your list:



Saving OAuth app


See that client ID? Copy it. That's what we're going to use to tell our React Native app which Wix site to connect to.


Now, let's code!



Use the Wix SDK to connect with your store


Connecting to your site is a really simple endeavor thanks to the Wix SDK. In short, the Wix SDK is a plug & play client that lets you use the various Wix APIs through "Modules".


Since we want to show our product catalogue, we are going to need the Wix Stores module.


Therefore, let's install both the sdk and the stores module:



npm install @wix/sdk @wix/stores


Now, let's head back to the main page of our app at app/index.tsx. We need it to do two things:


  1. When the page loads, fetch the products list from Wix using the Wix SDK

  2. Store the products in a state, that we can use to show the products



export default function HomePage() {
  const [products, setProducts] = useState<ProductItemProps[]>([]);

  useEffect(() => {
    async function fetchProductsFromWix() {
      // fetch code will go here
    }

    fetchProductsFromWix();
  }, []);

  return (
    <SafeAreaView>
      <ProductsList products={products} />
    </SafeAreaView>
  );
}

We are now using the useState hook to hold our products list. We will use the useEffect hook to load them, and finally pass the products state to the <ProductsList /> component.


Now, we need to create our Wix SDK client in order to connect with our Wix Studio site. We need to import the following:


import { createClient, OAuthStrategy } from '@wix/sdk';
import * as stores from '@wix/stores';


And update our component in the following way:



export default function HomePage() {
  const [products, setProducts] = useState<ProductItemProps[]>([]);

  useEffect(() => {
    async function fetchProductsFromWix() {
      const wix = createClient({
        auth: OAuthStrategy({ clientId: 'YOUR CLIENT ID' }),
        modules: { stores },
      });
    }

    fetchProductsFromWix();
  }, []);

  return (
    <SafeAreaView>
      <ProductsList products={products} />
    </SafeAreaView>
  );
}


Make sure to replace "YOUR CLIENT ID" with the client id you got in the previous section.


Now all that's left to do is to fetch the products and store them in the products state. Luckily, there's no hassle involved in that:


export default function HomePage() {
  const [products, setProducts] = useState<ProductItemProps[]>([]);

  useEffect(() => {
    async function fetchProductsFromWix() {
      const wix = createClient({
        auth: OAuthStrategy({ clientId: 'YOUR CLIENT ID' }),
        modules: { stores },
      });

      const response = await wix.stores.products.queryProducts().find();

      setProducts(
        response.items.map((item) => ({
          title: item.name!,
          description: item.description!,
          price: item.priceData!.formatted!.price!,
          image: item.media!.mainMedia!.image!.url!,
        })),
      );
    }

    fetchProductsFromWix();
  }, []);

  return (
    <SafeAreaView>
      <ProductsList products={products} />
    </SafeAreaView>
  );
}


We do two things here:


  1. Pull the list of products by using the stores.products.queryProducts api.

  2. Store them in the products state.


Finally, refresh your app. Tada!



List of wix products

Success! Your brand new React Native app is now connected to your Wix Studio site. Congratulations!



Finish your app


You’ve now connected your store to your app. Use Wix Headless’ APIs to finish your app:




Want more ? Join our Discord community Dev on Wix.

Find new ways FWD

Thanks for submitting!

By subscribing, you agree to receive the Wix Studio newsletter and other related content and acknowledge that Wix will treat your personal information in accordance with Wix's Privacy Policy.

Do brilliant work—together

Collaborate and share inspiration with other pros in the Wix Studio community.

Image showing a photo of a young group of professionals on the left and a photo highlighting one professional in a conference setting on the right