🛍️ Build a commerce storefront

jaepass - Oct 20 '20 - - Dev Community

Alt Text

Project Goal: Build an e-commerce web store with a products listing.

What you’ll learn: Setting up your React app, API basics, React components basics, fetch and display products data from an external API.

Tools you’ll need: A modern browser like Chrome to access CodeSandbox - be sure to create an account in CodeSandbox to keep the versions of your work intact.

Time needed to complete: 30 minutes
Just want to try the app?: CodeSandbox link

The main objective here is to learn React fundamentals in conjunction with working with an API to build an e-commerce application! We're going to create a real-world app fetching data from an external API to list products in a product catalogue page! We're really excited so let's get right to it!

Here is a summary of what we will achieve!

  • Go over React basics
  • Create components in React
  • Fetch data from an external API data source called Chec
  • Use an axios-based library, Commerce.js, to add eCommerce logic
  • List products on a products catalogue page

Check out this live demo sneakpeek to have a look at what we're building today!

Prerequisites

This project assumes you have some knowledge of the below concepts before starting:

  • Some basic knowledge of JavaScript fundamentals
  • Some basic knowledge of JavaScript frameworks
  • An idea of the JAMstack architecture and how APIs work

Getting Started

We mentioned you needing Code Sandbox above, so what exactly is it? Codesandbox is an online IDE (Integrated Development Environment) playground that allows you to develop your project easily in the browser without having to set up your development environment.

So that's exactly what we're going to do. Head on over to CodeSandbox and create your account if you haven't already. Create a CodeSandbox account and scaffold a starter React template by clicking here. Choosing a React template in codesandbox or downloading it as a dependency is the same idea as installing create-react-app and getting a starting boilerplate of a single page application. You can read more about Create React App here.

Basic React App Structure:

In most cases when you scaffold a React project, a typical project structure would look like this.

  • my-app/
    • README.md
    • node_modules/
    • package.json
    • public/
    • index.html
    • favicon.ico
    • src/
    • App.css
    • App.js
    • App.test.js
    • index.css
    • index.js
    • logo.svg

The public folder contains our assets, html static files and custom client side javascript files. package.json is used by npm (Node package manager) to save all the packages needed to deploy our app, but we don't have to worry about this because CodeSandbox installs and updates this file for us.

In our public, we have a standard html file called index.html. This is our point of entry file where we have our root element, which is named by convention. If you scroll down to line 30 in the body element, you will see <div id="root"></div>. This is the root element where we will be injecting our application.

The src folder contains all our React code and houses our index.js, app.js and later on our components when we start to create them. The fileindex.js is opened by default. You will see something like this:

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";

const rootElement = document.getElementById("root");

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  rootElement
);
Enter fullscreen mode Exit fullscreen mode

Here we import the React library and we use the ReactDOM render() method in order to print the contents of our App component into the root div in our index.html that we specified above. Our main app component App.js has to be imported as well to be included in the render. The App.js component is passed in as the first argument in the render function and the rootElement as the second argument. That will tell React to render the app component and transform it into an element using the React.createElement method at build time to the index page. We will be stripping out all the scaffolded code in the component App.js and rebuilding later on.

import React from "react";
import "./styles.css";

export default function App() {
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <h2>Start editing to see some magic happen!</h2>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The App function in App.js represents a React component. You can create your components as a individual files (Single File Component - SFC). In React, html-like tags which are what we call JSX can be passed in the render function and be returned. The JSX inside the return function is what the App.js will render out.

đź’ˇ Tip

What are Components?

Components sections of your application that you extract out
into separate files so that you can make them reusable. There are two types of components, functional components and class components. We will get into these different components a bit more later in the tutorial.

Now that we've walked through the starting structure in a React application, this is where the real fun begins. As you know we will be building a real-world e-commerce application sourcing data from an API data source. In order to do that, we will need to install a package dependency. So lets get right to it!

Install our commerce API

We will be using a commerce API platform to source our products data. The commerce backend we will be using is called Chec and it comes with the handy Commerce.js SDK packed with helper functions to handle our commerce logic in the frontend seamlessly.

đź’ˇ Tip

What are APIs and SDKs?

API stands for Application Programming Interface and acts as a "contract" between client and server. The client which is a browser or any front-facing layer makes a request to a server to receive a response or initiate a defined action. When a platform has an API, it allows a software or front-facing client to interact with its data. SDK stands for Software Development Kit and is a installable package of development tools that typically comes with a library, a debugger, and other common tooling.

In a standard local development environment, the Chec/Commerce.js SDK can be installed in two ways:

  1. Install the package via package manager either with npm npm install @chec/commerce.js or yarn yarn @chec/commerce.js
  2. Install via CDN by included this script <script type="text/javascript" src="https://cdn.chec.io/v2/commerce.js"></script> in the index.html file.

Since we are using Codesandbox, we can conveniently add a dependency on the left sidebar. So let's go ahead and do that! Click on Add dependency and in the search field type in @chec/commerce.js and select the option which is the latest 2.1.1 version.

đź’ˇ Tip

The Commerce.js SDK is using the axios library under the hood. Axios is a promise-based HTTP client that works both in the browser and in other node.js environments.

Link up our Commerce instance

The Commerce.js SDK comes packed with all the frontend oriented functionality to get a customer-facing web-store up and running. In order to utilize all the features of this commerce platform's SDK, we are going to import the module into a folder called lib so that we can have access to our Commerce object instance throughout our application.

Let's go ahead and do that right now! In your src directory, we'll create a new folder called lib, create a file commerce.js and copy and paste the below code in it. Typically a lib folder in a project stores files that abstracts functions or some form of data.

// src/lib/Commerce.js
import Commerce from '@chec/commerce.js';
export const commerce = new Commerce('pk_17695092cf047ebda22cd36e60e0acbe5021825e45cb7');
Enter fullscreen mode Exit fullscreen mode

Ok so what have we done here? First we import in the Commerce.js module which we will be using to communicate with the API platform, then we export an instance of Commerce and pass in a public key. The public key is needed to give us access to data in the Chec API.

đź’ˇ **Tip

Please note that for the purpose of getting you up and running with an account with products data, a public key is provided from a demo merchant account. A token key acess is what gives the API an authentication scope. A public key will give us access to Chec's core API resources such as your products and cart data.

Now that we've installed our commerce SDK and created our Commerce instance, we now have access to the Commerce object throughout our application!

Make your first request to fetch the products data

Commerce.js was built with all the frontend functionalities you would need to build a complete eCommerce store. All you need to do is make requests to various Chec API endpoints, receive successful responses, then you have your raw data to output beautifully onto your web store.

One of the main resources in Chec is the Products endpoint. Commerce.js
makes it seamless to fetch product data with its promise-based
method commerce.products.list(). This request would make a
call to the GET v1/products API endpoint and return a list of product data. Open up your App.js file and delete the code that came with creating a new React app and we will write this file from scratch.

Import commerce as well as a ProductsList component which you'll create in the next
section. While there, initialize an empty array products state in your constructor.

// src/App.js
import React, { Component } from 'react';
import { commerce } from './lib/commerce';
import ProductsList from './components/ProductsList';
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      products: [],
    }
  }
  render() {
    return (
      <div className="app">
      </div>
    );
  }
};
export default App;
Enter fullscreen mode Exit fullscreen mode

In React, when a component is created, the constructor is the first method called. Initializing your state in the constructor will allow you to store data on the component's instance when it's created. We also need to pass props in as a parameter in the the constructor method and call the super() method in order to make the this.props object available. The super() method then calls the constructor of the parent class which is our class component in this case. You'll initialize products
as an empty array in your app to be able to store the product data later on.

đź’ˇ Tip

The constructor method in a React class component gets called before the component gets mounted and helps to initialize local states or bind event handlers in the object you're creating. You would only need to define a constructor if you need to maintain some form of state in your React component.

You will be creating the products components as stateful components. This means that the components has the ability to keep track of changing data. You might ask why would be want to keep track of changing data. Any commerce store needs to have the ability to update its products listing in real-time. Be it new products being added, products being sold out, or products being taken off. The API data constantly will get updated, therefore the UI has to be reactive.

You can now make your first Commerce.js request! Create a function called fetchProducts() in the component and make a request to the products endpoint using the Commerce.js method commerce.products.list().

/**
 * Fetch products data from Chec and stores in the products data object.
 * https://commercejs.com/docs/sdk/products
 */
fetchProducts() {
  commerce.products.list().then((products) => {
    this.setState({ products: products.data });
  }).catch((error) => {
    console.log('There was an error fetching the products', error);
  });
}
Enter fullscreen mode Exit fullscreen mode

Inside the function, use the commerce object to access the products.list() method for access to product data. commerce.products.list() is a
promise-based function call that will resolve the request and then() sets the response data with this.setState() into
the products state key created earlier in the component's constructor. The catch() method catches any errors in the
case that the request to the server fails.

Of course simply creating the function does not do anything as you have yet to call this function. When the app
component mounts to the DOM, use the lifecycle hook componentDidMount() to fetch your data. It is a React lifecycle method that helps to call functions when the component first mounts to the DOM. Since we are loading data from a remote endpoint, we want to invoke the fetchProducts() function to update the state with the returned products products so that we can render our updated data.

componentDidMount() {
  this.fetchProducts();
}
Enter fullscreen mode Exit fullscreen mode

Speaking of render, you are going to need one of the core React functions render(). Without render() and a return statement, nothing
would get logged onto your frontend. Below is the expected returned data (abbreviated):

[
  {
    "id": "prod_NqKE50BR4wdgBL",
    "created": 1594075580,
    "last_updated": 1599691862,
    "active": true,
    "permalink": "TSUTww",
    "name": "Kettle",
    "description": "<p>Black stove-top kettle</p>",
    "price": {
      "raw": 45.5,
      "formatted": "45.50",
      "formatted_with_symbol": "$45.50",
      "formatted_with_code": "45.50 USD"
    },
    "quantity": 0,
    "media": {
      "type": "image",
      "source": "https://cdn.chec.io/merchants/18462/images/676785cedc85f69ab27c42c307af5dec30120ab75f03a9889ab29|u9 1.png"
    },
    "sku": null,
    "meta": null,
    "conditionals": {
      "is_active": true,
      "is_free": false,
      "is_tax_exempt": false,
      "is_pay_what_you_want": false,
      "is_quantity_limited": false,
      "is_sold_out": false,
      "has_digital_delivery": false,
      "has_physical_delivery": false,
      "has_images": true,
      "has_video": false,
      "has_rich_embed": false,
      "collects_fullname": false,
      "collects_shipping_address": false,
      "collects_billing_address": false,
      "collects_extrafields": false
    },
    "is": {
      "active": true,
      "free": false,
      "tax_exempt": false,
      "pay_what_you_want": false,
      "quantity_limited": false,
      "sold_out": false
    },
    "has": {
      "digital_delivery": false,
      "physical_delivery": false,
      "images": true,
      "video": false,
      "rich_embed": false
    },
    "collects": {
      "fullname": false,
      "shipping_address": false,
      "billing_address": false,
      "extrafields": false
    },
    "checkout_url": {
      "checkout": "https://checkout.chec.io/TSUTww?checkout=true",
      "display": "https://checkout.chec.io/TSUTww"
    },
    "extrafields": [],
    "variants": [],
    "categories": [
      {
        "id": "cat_3zkK6oLvVlXn0Q",
        "slug": "office",
        "name": "Home office"
      }
    ],
    "assets": [
      {
        "id": "ast_7ZAMo1Mp7oNJ4x",
        "url": "https://cdn.chec.io/merchants/18462/images/676785cedc85f69ab27c42c307af5dec30120ab75f03a9889ab29|u9 1.png",
        "is_image": true,
        "data": [],
        "meta": [],
        "created_at": 1594075541,
        "merchant_id": 18462
      }
    ]
  },
]
Enter fullscreen mode Exit fullscreen mode

The data object contains all the property endpoints such as the product name, the product description, product price or any uploaded variants or assets. This data is exposed when you make a request to the API. As mentioned above, Commerce.js is a Software Development Kit(SDK) that comes with abstracted axios promise-based function calls that will help to fetch data from the endpoints. The public key access that we briefed over above is a public token key from a merchant store. This account already has products and products information uploaded to the Chec dashboard for us to run a demo store with.

Now add the empty <ProductsList
/>
component to your render function:

render() {
  const { products } = this.state;
  return (
    <div className="app">
      <ProductsList />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Destructure products from state to make it a little cleaner. You'll need to pass the products property as an argument
to your ProductsList component. This means that the value of the ProductsList component's prop
products will be resolved from the parent (App) component's state, and will update automatically whenever it changes.

Start to style your components

Before we go any further, let's start to port in some styles so we can start to make our UI look slick! We will be using SCSS, a CSS style compiler to style our application. Please note that we will not be going into styling details but will only go over the high-level of porting in the styles. First install node-sass by adding it as a dependency in the left sidebar or alternatively in a local environment by running the command below.

yarn add node-sass
# OR
npm install node-sass
Enter fullscreen mode Exit fullscreen mode

Next, let's go ahead and create a styles folder with a scss folder inside. Inside of the scss folder, create two other folders named components and global. Lastly, still in the scss folder, create a file and name it styles.scss. This file is where we will import in all our components and global styles. Your styles structure should look like the below tree.

  • src/
    • styles/
    • components/
    • global/
    • styles.scss

In the components folder, create a file named _products.scss and copy in the below code.

/* _products.scss */
.products {
    display: block;
    margin: 3rem;
    @include md {
        display: grid;
        grid-template-columns: repeat(3, minmax(0, 1fr));
        margin: 10rem;
    }
    .product {
        &__card {
            width: 55%;
            margin: auto;
            margin-top: 0;
            margin-bottom: 0;
            padding-bottom: 2rem;
        }
        &__image {
            border: 2px solid $text-primary;
            width: 90%;
        }

        &__name {
            color: $text-primary;
            padding-top: 1rem;
            padding-bottom: 0.25rem;
        }

        &__details {
            display: flex;
            justify-content: space-between;
            margin-top: 0.75rem;
        }

        &__price {
            align-self: center;
            margin: 0;
            color: $text-grey;
        }


        &__details {
            display: flex;
            justify-content: space-between;
        }

        &__btn {
            background: $color-accent;
            color: white;
            font-size: 0.75rem;
            text-transform: uppercase;
            padding: 0.5rem 1rem;
            transition: all 0.3s ease-in-out;
            margin-top: 1rem;
            border: none;

            &:hover {
                background-color: lighten(#EF4E42, 5);
            }
            @include sm {
                margin-top: 0;
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Now in the global folder, create _base.scss, _body.scss and _mixins.scss and copy in the respective code below.

/* _base.scss */
// Font styles
$font-primary: 'Amiko', sans-serif;
$font-secondary: 'Adamina', serif;
// Colors
$bg-color: #E8E2D7;
$text-primary: #292B83;
$text-grey: rgb(67, 67, 67);
$color-accent: #EF4E42;
// Media query sizes
$sm-width: 576px;
$md-width: 768px;
$lg-width: 992px;
$xl-width: 1200px;
Enter fullscreen mode Exit fullscreen mode
/* _body.scss */
body {
  font-family: $font-primary;
  background-color: $bg-color;
}
Enter fullscreen mode Exit fullscreen mode
/* _mixins.scss */
@mixin small-xs {
  @media (max-width: #{$sm-width}) {
    @content;
  }
}
@mixin sm {
  @media (min-width: #{$sm-width}) {
    @content;
  }
}
@mixin md {
  @media (min-width: #{$md-width}) {
    @content;
  }
}
@mixin lg {
  @media (min-width: #{$lg-width}) {
    @content;
  }
}
@mixin xl {
  @media (min-width: #{$xl-width}) {
    @content;
  }
}
@mixin md-max {
  @media (max-width: #{$lg-width}) {
    @content;
  }
}
Enter fullscreen mode Exit fullscreen mode

Lastly as mentioned, you'll need to now import those created files in the style index styles.scss.

@import "global/base";
@import "global/body";
@import "global/mixins";
@import "components/product";
Enter fullscreen mode Exit fullscreen mode

Now that all the styles are written and imported, you should start to see the styles pull through when you render your components later.

Create our product item component

The nature of React and most modern JavaScript frameworks is to separate your code into components. Components are a way to encapsulate a group of elements for reuse throughout your application. You'll be creating two components for products, one will be for the single product item and another for the list of product items.

Start by creating a class component and name it ProductItem.js in src/components. This component will render the individual product card. In your render function destructure product from your props. You will reference this
property to access each product's image, name, description, and price via .media.source, .name, .description and .price in the return statement.

Product descriptions return HTML. To strip HTML from the product description string, using this string-strip-html handy library will do the trick. Install this library by running yarn add string-strip-html or npm i string-strip-html. After installing, import the module in and pass in the product description to the stripHtml function.

import React, { Component } from "react";
import stripHtml from 'string-strip-html';
class ProductItem extends Component {
  render() {
    const { product } = this.props
    const { result } = stripHtml(product.description);
    return (
      <div className="product__card">
        <img className="product__image" src={product.media.source} alt={product.name} />
        <div className="product__info">
          <h4 className="product__name">{product.name}</h4>
          <p className="product__description">
            {/* product description stripped of html tags */}
            {result}
          </p>
          <div className="product__details">
            <p className="product__price">
            {product.price.formatted_with_symbol}
            </p>
          </div>
        </div>
      </div>
    );
  }
};
export default ProductItem;
Enter fullscreen mode Exit fullscreen mode

As you saw earlier in the abbreviated JSON, the returned product data object comes with all the information that you
need to build a product listing view. In the code snippet above, your product prop is being used to access the various
properties. First, render an image tag with the src value of product.media.source as the values inside the curly
braces dynamically binds to the attributes.

Create our products list component

It's now time to create a ProductsList.js component inside src/components. The ProductsList component will be another
class component which will loop through and render a list of ProductItem components.

First, import in the ProductItem component. Next, define a products prop. This will be provided by the parent component.

In your return statement you need to use the map function
to render a ProductItem component for each product in your products prop. You also need to pass in a unique identifier (product.id) as the key attribute - React will use it to determine which items in a list have changed and which parts of your application need to be re-rendered.

import React, { Component } from 'react';
import ProductItem from './ProductItem';
class ProductsList extends Component {
  render() {
    const { products } = this.props;
    return (
      <div className="products">
        {products.map((product) => (
          <ProductItem
            key={product.id}
            product={product}
          />
        ))}
      </div>
    );
  }
}
export default ProductsList;
Enter fullscreen mode Exit fullscreen mode

This component will be a bit bare-boned for now except for looping through a ProductItem component.

With both your product item and list components created, go back to App.js to render the <ProductsList /> and pass in the products prop with the returned product data as the value.

import React, { Component } from 'react';
import { commerce } from './lib/commerce';
import ProductsList from './components/ProductsList';
class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      products: [],
    }
  }
  componentDidMount() {
    this.fetchProducts();
  };
  /**
   * Fetch products data from Chec and stores in the products data object.
   * https://commercejs.com/docs/sdk/products
   */
  fetchProducts() {
    commerce.products.list().then((products) => {
      this.setState({ products: products.data });
    }).catch((error) => {
      console.log('There was an error fetching the products', error);
    });
  }
  render() {
    const { products } = this.state;
    return (
      <div className="app">
        <ProductsList
          products={products}
        />
      </div>
    );
  }
};
export default App;
Enter fullscreen mode Exit fullscreen mode

Conclusion

Awesome, there you have it! You have just created an e-commerce React application listing products on from an API backend! The next steps would be to add cart and checkout functionality to your application. Stay tuned for follow up workshops!

Author

Made with ❤️ by Jaeriah Tay

. . . . .