How To Build an Ecommerce App With React

Ashutosh K Singh

In this media jam, we will discuss how to build an ecommerce app in React using Airtable, Cloudinary and Snipcart.

We will use Airtable to store product data and use Cloudinary's Product Gallery to showcase different product images. In this tutorial, we will build an ecommerce site for a cake shop named Sweet Tooth and use various cakes as products.

If you want to jump right into the code, check out the GitHub Repo here.

CodeSandbox

Setup

Run the following command to create the initial React app with CRA (create-react-app) template.

1npx create-react-app react-ecommerce-app
2cd react-ecommerce-app
3npm start

The last command, npm start, will start the development server on your system's port 3000. You can stop the server by hitting CTRL+C in the terminal.

The next step is to install the dependencies that you will need in this project. Run the following command to install them.

1npm i airtable cloudinary-react

In this project, you will use Tailwind CSS to style the ecommerce app. Its installation and configuration are beyond the scope of this media jam; you can refer to the official documentation to install Tailwind CSS before proceeding further.

Run the following command to create a file named .env to store your API keys securely.

1touch .env

Head over to https://app.snipcart.com/register in your browser and create a free account on Snipcart.

After creating the Snipcart account, head over to account settings and copy the PUBLIC TEST API KEY under Credentials.

Login into your https://cloudinary.com/ and copy the Cloud Name from your dashboard. All the product images will be stored in Cloudinary.

Create a folder named sweet-tooth in your Cloudinary account and upload Butter Cake's image into it. You can download the image from here. After uploading the image, copy its public_id.

You will use Airtable to store the product's data. Create an account on Airtable if you haven't already.

Every product will at least have the following fields:

  • id - Unique ID of the product. - Number
  • name - Name of the product. - Single Line Text
  • description - Short description of the product. - Long Text
  • price - Price of the product. - Currency
  • public_id - Public Id of product's image copied from Cloudinary. - Single Line Text

You can add more fields like stock, ratings, feedback, etc. if you want.

Create a workspace named Sweet Tooth in Airtable and a base named cakes inside the Sweet Tooth workspace. Add the following data of Butter Cake to it.

  • id - 1
  • name - Butter Cake
  • description - A butter cake is a cake in which one of the main ingredients is butter.
  • price - $30.00
  • public_id - sweet-tooth/{YOUR-PRODUCT'S-PUBLIC-ID}

You can copy the data used in this project from https://airtable.com/shre2MWIyxJgAepfC.

After adding the record for Butter Cake, head over to https://airtable.com/api and select the base you just created, i.e., Sweet Tooth, copy the base's id from the introduction section.

Navigate to https://airtable.com/account and copy your Airtable API Key.

Paste all the API Keys in the .env file.

1REACT_APP_CLOUDINARY_CLOUD_NAME =''
2REACT_APP_AIRTABLE_BASE_ID = ''
3REACT_APP_AIRTABLE_API_KEY =''
4REACT_APP_SNIPCART_KEY=''

Update your index.html to include Cloudinary's Product Gallery JavaScript file and Snipcart's CSS and JavaScript like this.

1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="utf-8" />
6 <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
7 <meta name="viewport" content="width=device-width, initial-scale=1" />
8 <meta name="theme-color" content="#000000" />
9 <meta name="description" content="React Ecommerce App" />
10 <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
11 <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
12 <link rel="preconnect" href="https://app.snipcart.com">
13 <link rel="preconnect" href="https://cdn.snipcart.com">
14 <link rel="stylesheet" href="https://cdn.snipcart.com/themes/v3.2.0/default/snipcart.css" />
15 <script src="https://product-gallery.cloudinary.com/all.js" type="text/javascript"></script>
16
17 <title>Sweeth Tooth</title>
18</head>
19
20<body>
21 <noscript>You need to enable JavaScript to run this app.</noscript>
22 <div id="root"></div>
23 <script async src="https://cdn.snipcart.com/themes/v3.2.0/default/snipcart.js"></script>
24 <div hidden id="snipcart" data-api-key="%REACT_APP_SNIPCART_KEY%" data-config-modal-style="side"></div>
25</body>
26
27</html>

How To Fetch and Display the Products

In this section, you will fetch the data from Airtable using the airtable javascript library and then display it on the app. To display the product image, you will use the Image component from the cloudinary-react package.

Modify src/App.js like this.

1import React, { useEffect, useState } from "react";
2import { Image, Transformation } from "cloudinary-react";
3var Airtable = require("airtable");
4
5var base = new Airtable({
6 apiKey: process.env.REACT_APP_AIRTABLE_API_KEY,
7}).base(process.env.REACT_APP_AIRTABLE_BASE_ID);
8
9function App() {
10 const [cakes, setCakes] = useState({});
11
12 useEffect(() => {
13 base("cakes")
14 .select({
15 view: "Grid view",
16 })
17 .firstPage( (err, records)=> {
18 if (err) {
19 console.error(err);
20 return;
21 }
22 setCakes(records);
23 });
24 }, []);
25
26 return (
27 <div className="bg-pink-100 min-h-screen">
28 <div className="container px-3 mx-auto">
29 <h1 className=" text-5xl md:text-7xl text-red-800 px-4 pt-2 leading-normal mb-2">
30 Sweet Tooth
31 </h1>
32 {cakes && console.log(cakes)}
33 </div>
34 </div>
35 );
36}
37
38export default App;

You start by importing useState() and useEffect() hooks. You also import Image and Transformation components from cloudinary-react; these components display the product's image on the app.

You create an instance of airtable and pass your Airtable API Key and base's id to it. Using the useState() hook, you create a state named cakes with an initial value of an empty array. This state will store the data fetched from Airtable.

To fetch the data, you use the select method to retrieve the data in cakes. The select returns a query object. To fetch the records matching that query, you use the firstPage method of the query object. If a successful response is returned from the firstPage method, you store the fetched records in the cakes state using the setCakes() method.

You create a simple layout for your ecommerce app using Tailwind CSS and logs the cakes state in the console using the JavaScript expression {cakes && console.log(cakes)}.

Start your development server and navigate to http://localhost:3000/ in your browser. Here is how your app will look like.

Here is how the product data stored in the cakes array looks like.

1{
2 "_table": {
3 "_base": {
4 "_airtable": {},
5 "_id": "app2dz674vyBaqHT8"
6 },
7 "id": null,
8 "name": "cakes"
9 },
10 "id": "recxVHtospJjb0tYP",
11 "_rawJson": {
12 "id": "recxVHtospJjb0tYP",
13 "fields": {
14 "price": 30,
15 "description": "A butter cake is a cake in which one of the main ingredients is butter. \n",
16 "id": 1,
17 "name": "Butter Cake",
18 "public_id": "sweet-tooth/bl7idqn4103cdpmctau7"
19 },
20 "createdTime": "2021-05-23T09:50:03.000Z"
21 },
22 "fields": {
23 "price": 30,
24 "description": "A butter cake is a cake in which one of the main ingredients is butter. \n",
25 "id": 1,
26 "name": "Butter Cake",
27 "public_id": "sweet-tooth/bl7idqn4103cdpmctau7"
28 }
29}

Now that you have the product's data, the next step is to iterate over the data array or cakes array and display them on the app. For this, you will create a Product card and reuse it with different product's data.

Remove the JavaScript expression {cakes && console.log(cakes)}. Add the following code under the title, Sweet Tooth or the <h1> tag.

1<div className="container flex flex-wrap -mx-2 px-4 overflow-hidden">
2 {cakes.length > 0 &&
3 cakes.map((cake) => (
4 <div
5 className="my-2 px-2 w-full overflow-hidden sm:w-full md:w-1/2 lg:w-1/3 xl:w-1/3 rounded-xl shadow-4xl"
6 key={cake.fields.id}
7 >
8 <Image
9 className="rounded-t-xl"
10 cloudName={process.env.REACT_APP_CLOUDINARY_CLOUD_NAME}
11 publicId={cake.fields.public_id}
12 format="webp"
13 secure="true"
14 >
15 <Transformation quality="60" />
16 </Image>
17 <div className="-mt-1 rounded-b-xl bg-white px-4 py-3">
18 <div className="px-1">
19 <button className="text-4xl text-gray-600 font-medium underline hover:text-pink-600">
20 {cake.fields.name}
21 </button>
22 <p className="text-md py-4 leading-6 overflow-ellipsis text-gray-600">
23 {cake.fields.description}
24 </p>
25 </div>
26
27 <div className="px-6 py-3">
28 <p className="text-3xl inline-block px-2 text-gray-800">
29 {cake.fields.price}$
30 </p>
31
32 <button
33 className="snipcart-add-item text-white float-right flex flex-auto justify-center items-center uppercase text-md px-2 bg-green-400 hover:bg-green-500 w-36 h-12 border-transparent font-medium rounded-3xl"
34 data-item-id={cake.fields.id}
35 data-item-price={cake.fields.price}
36 data-item-url="/"
37 data-item-description={cake.fields.description}
38 data-item-image={`https://res.cloudinary.com/${process.env.REACT_APP_CLOUDINARY_CLOUD_NAME}/image/upload/q_10/${cake.fields.public_id}`}
39 data-item-name={cake.fields.name}
40 >
41 <svg
42 xmlns="http://www.w3.org/2000/svg"
43 className="h-7 w-7 mr-1"
44 fill="none"
45 viewBox="0 0 24 24"
46 stroke="currentColor"
47 >
48 <path
49 strokeLinecap="round"
50 strokeLinejoin="round"
51 strokeWidth={2}
52 d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z"
53 />
54 </svg>
55 buy now
56 </button>
57 </div>
58 </div>
59 </div>
60 ))}
61</div>

You start by creating a responsive flex div with Tailwind CSS and then map over the cakes array inside it. You display the product's image using the Image component and reduce its quality using the Transformation component. You pass the public_id of the product's image stored in Cloudinary and your Cloudinary cloud name to the Image component.

1<Image
2 className="rounded-t-xl"
3 cloudName={process.env.REACT_APP_CLOUDINARY_CLOUD_NAME}
4 publicId={cake.fields.public_id}
5 format="webp"
6 secure="true"
7>
8 <Transformation quality="60" />
9</Image>

You display the product's name inside a button; in the next section, you will use it to open a modal containing the respective product's gallery. You also display the product's description and price in the product's card.

Here is how your ecommerce app will look like.

In the buy now button, you pass the product's data required by Snipcart. You also pass your product's image URL generated with your Cloudinary cloud name and the public_id of the image. to Snipcart.

1<button
2 className="snipcart-add-item text-white float-right flex flex-auto justify-center items-center uppercase text-md px-2 bg-green-400 hover:bg-green-500 w-36 h-12 border-transparent font-medium rounded-3xl"
3 data-item-id={cake.fields.id}
4 data-item-price={cake.fields.price}
5 data-item-url="/"
6 data-item-description={cake.fields.description}
7 data-item-image={`https://res.cloudinary.com/${process.env.REACT_APP_CLOUDINARY_CLOUD_NAME}/image/upload/q_10/${cake.fields.public_id}`}
8 data-item-name={cake.fields.name}
9>
10 buy now
11</button>

When the user clicks the buy now button, the Snipcart cart comes into action. Users can use this cart to modify products in their cart and proceed to checkout.

How To Add Cloudinary Product Gallery

In this section, you will integrate Cloudinary's product gallery with your ecommerce app. Before using the product gallery, make sure the Settings → Restricted media types → Resource list is not selected in your account settings.

Your product gallery will need different images of the product, i.e., cakes. You will need to upload few pictures related to the product in your Cloudianry's account sweet-tooth folder. One way is to use the Upload Widget, search for the images online, and then upload them. You will also need to tag all the pictures with their respective product's name, as shown below.

In the App.js file, you will create two functions, openGallery(), which opens the product gallery, and closeGallery, which closes the product gallery. Since the product gallery will render inside a modal, you will need to define a state named showModal to show or hide the modal. You will also need to define another state, currentProduct, to track which product the user has selected.

The currentProduct state has an initial value of Butter Cake since you cant render the product gallery with empty strings, or your app will crash.

Add the following code after the initialization of cakes state in the App.js file.

1const [showModal, setShowModal] = useState(false);
2const [currentProduct, setCurrentProduct] = useState("Butter Cake");
3
4const myWidget = window.cloudinary.galleryWidget({
5 cloudName: process.env.REACT_APP_CLOUDINARY_CLOUD_NAME,
6 mediaAssets: [{ tag: currentProduct }],
7 secure: true,
8 aspectRatio: "16:9",
9 container: "#my-gallery",
10});
11myWidget.render();
12
13const openGallery = (selectedProduct) => {
14 setCurrentProduct(selectedProduct);
15 myWidget.update({ mediaAssets: [{ tag: currentProduct }] });
16 setShowModal(true);
17};
18
19const closeGalllery = () => {
20 myWidget.destroy();
21 setShowModal(false);
22};

You use the cloudinary.galleryWidget() method to initialize your product gallery with the following configuration.

  • cloudName - Name of your Cloudinary cloud.
  • mediaAssets - An array of assets. The individual assets in the array can be described either by an Asset object with tag of the images or by a PublicID string.
  • container - The containing element on the page for the Product Gallery widget. Here it is a div with id="my-gallery" inside the modal.
  • aspectRatio - The aspect ratio of the main viewer.

Since the modal will appear only when the button or product's name is clicked, you will not see the gallery although it has been rendered.

In the openGallery() function, you set the state of the currentProduct to selectedProduct, i.e., the product's name passed when the user clicks the respective product. You then update the gallery with the current product using the gallery's .update() method. Finally, the showModal state is set to true to display the modal and the gallery.

In the closeGallery() function, you destroy the product gallery with the .destroy() method and then close the modal by setting the showModal state to false. You can read more about Cloudinary product's gallery methods here.

The next step is to add the openGallery() function to the onClick event of the buy now button. Update the buy now button like this.

1<button
2 className="text-4xl text-gray-600 font-medium font-product-title underline hover:text-pink-600"
3 onClick={() => openGallery(cake.fields.name)}
4>
5 {cake.fields.name}
6</button>

The last step is to create the modal where the gallery will be shown. Add the following code before the third closing div at the bottom.

1{
2 showModal ? (
3 <>
4 <div className="justify-center items-center flex overflow-x-hidden overflow-y-auto fixed inset-0 z-50 outline-none focus:outline-none">
5 <div className="absolute w-4/6 my-8 mx-auto">
6 <div className="border-0 rounded-lg shadow-lg relative flex flex-col w-full bg-white outline-none focus:outline-none">
7 <div className="flex items-start justify-between p-4 border-b border-solid border-blueGray-200 rounded-t">
8 <h3 className="text-3xl text-pink-800 font-medium">
9 {currentProduct} Gallery
10 </h3>
11
12 <button
13 className="p-1 bg-transparent ml-auto border-0 text-red-600 float-right text-3xl leading-none font-semibold outline-none focus:outline-none"
14 onClick={() => {
15 closeGalllery();
16 }}
17 >
18 X
19 </button>
20 </div>
21 <div className="p-4" id="my-gallery"></div>
22 <div className="flex items-center justify-end p-4 border-t border-solid border-blueGray-200 rounded-b">
23 <button
24 className="text-red-500 background-transparent font-bold uppercase px-6 py-2 text-md outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150"
25 type="button"
26 onClick={() => {
27 closeGalllery();
28 }}
29 >
30 Close
31 </button>
32 </div>
33 </div>
34 </div>
35 </div>
36 <div className="opacity-25 fixed inset-0 z-40 bg-black"></div>
37 </>
38 ) : null
39}

In the above code, the modal appears according to the state showModal. Inside the modal, you have created the div with id="my-gallery", where the product gallery is rendered. You have also created two buttons to close the modal, namely X and CLOSE; when these buttons are clicked, the closeGallery() function is executed, hiding the modal and destroying the product gallery to avoid memory leakage and performance issues.

Conclusion

In this media jam, we discussed how to build an ecommerce app in React using Airtable, Cloudinary, and Snipcart. We also saw how to integrate Cloudinary's product gallery with a react application.

In this tutorial, we used Tailwind CSS to style our app; you can use any React UI library like Chakra UI, Material UI, React Bootstrap, etc.

Here are some additional resources that can be helpful:

Happy coding!

Ashutosh K Singh

JavaScript Developer

I'm a JavaScript Developer & Technical Writer. I develop awesome stuff with JavaScript and love to write about them.