Create Your Own eCommerce Website With React

Engineering Oct 08, 2021

React is loved for its speed and component-based structure. Due to this, it is widely used in eCommerce stores where performance is the topmost priority. In this article, we will see how you can create your own simple eCommerce website with React js and Shopify.

Create Your Own eCommerce Website With React

Getting started
Before writing the code, we have to setup the project and install some dependencies. To create a new react project, enter the following command in the terminal.

npx create-react-app shopify-react

After the project is installed, paste the following dependencies in package.json.

"dependencies": {
   "@material-ui/core": "^4.9.0",
   "@testing-library/jest-dom": "^4.2.4",
   "@testing-library/react": "^9.3.2",
   "@testing-library/user-event": "^7.1.2",
   "atomize": "^1.0.20",
   "react": "^16.12.0",
   "react-dom": "^16.12.0",
   "react-router-dom": "^5.1.2",
   "react-scripts": "3.3.1",
   "react-transition-group": "^4.3.0",
   "shopify-buy": "^2.9.0",
   "styletron-engine-atomic": "^1.4.4",
   "styletron-react": "^5.2.6"
 },

Now run yarn install to install these packages in your local machine.

Writing the code
Now that we have all the desired packages installed in our personal machine, let us create the following folders in /src directory.

The most important thing is our shop data that will be rendered inside the components. Let us create a shop context. Create a new file shopContext.js in /src/context and enter the following code inside it.

import React, { Component } from "react";
import Client from "shopify-buy";
 
const ShopContext = React.createContext();
 
const client = Client.buildClient({
 storefrontAccessToken: "key", // your key goes here
 domain: "abc.myshopify.com", //your token goes here.
});
 
class ShopProvider extends Component {
 state = {
   products: [],
   product: {},
   checkout: {},
   isCartOpen: false,
   };
 
 componentDidMount() {
 
   if (localStorage.checkout) {
     this.fetchCheckout(localStorage.checkout);
   } else {
     this.createCheckout();
   }
 
 }
 
 createCheckout = async () => {
   const checkout = await client.checkout.create();
   localStorage.setItem("checkout", checkout.id);
   await this.setState({ checkout: checkout });
 };
 
 fetchCheckout = async (checkoutId) => {
   client.checkout
     .fetch(checkoutId)
     .then((checkout) => {
       this.setState({ checkout: checkout });
     })
     .catch((err) => console.log(err));
 };
 
 addItemToCheckout = async (variantId, quantity) => {
   const lineItemsToAdd = [
     {
       variantId,
       quantity: parseInt(quantity, 10),
     },
   ];
   const checkout = await client.checkout.addLineItems(
     this.state.checkout.id,
     lineItemsToAdd
   );
this.setState({ checkout: checkout });
   console.log(checkout);
 
   this.openCart();
 };
 
 fetchAllProducts = async () => {
   const products = await client.product.fetchAll();
   this.setState({ products: products });
 };
 
 fetchProductWithId = async (id) => {
   const product = await client.product.fetch(id);
   this.setState({ product: product });
   console.log(JSON.stringify(product));
 
   return product;
 };
 
 closeCart = () => {
   this.setState({ isCartOpen: false });
 };
 openCart = () => {
   this.setState({ isCartOpen: true });
 };
 
 render() {
   return (
     <ShopContext.Provider
       value={{
         ...this.state,
         fetchAllProducts: this.fetchAllProducts,
         fetchProductWithId: this.fetchProductWithId,
         closeCart: this.closeCart,
         openCart: this.openCart,
         addItemToCheckout: this.addItemToCheckout,
       }}
     >
     {this.props.children}
     </ShopContext.Provider>
   );
 }
}
 
const ShopConsumer = ShopContext.Consumer;
 
export { ShopConsumer, ShopContext };
 
export default ShopProvider;

Here, we have created a global context provider that will be holding the state for our products and cart. We will wrap our whole application with ShopProvider. Other than this, we are using builtin fucntions to get cart that are provided by shopify library.

Now, let us create our HomePage.js and ProductPage.js in pages folder

Product.js

import React, { useEffect, useContext } from 'react'
import { useParams } from 'react-router-dom'
import { ShopContext } from '../context/shopContext'
import { Text, Div, Button, Row, Col, Container } from 'atomize'
import Loading from '../components/Loading'
 
const ProductPage = () => {
   let { id } = useParams()
   const { fetchProductWithId, addItemToCheckout, product } = useContext(ShopContext)
 
   useEffect(() => {
       fetchProductWithId(id)
      
       // fetchData()
       return () => {
           // setProduct(null)
       };
   }, [ fetchProductWithId, id])
 
   if (!product.title) return <Loading />
   return (
       <Container>
           <Row m={{ b: "2rem" }} p="2rem">
               <Col>
                   <Div bgImg={product.images[0].src} shadow="3" bgSize="cover" w="100%" bgPos="center center" h="40rem"/>
               </Col>
               <Col>
                   <Text tag="h1" textColor="black500" textWeight="200" m={{ y: '2rem' }}>{product.title}</Text>
                   <Text tag="h3" m={{ y: '2rem' }} textWeight="200">${product.variants[0].price}</Text>
                   <Text tag="p" textSize="paragraph" textColor="gray900" textWeight="200">{product.description}</Text>
                   <Button rounded="0" shadow="3" bg="black500" m={{ y: '2rem' }} onClick={() => addItemToCheckout(product.variants[0].id, 1)}>Add To Cart</Button>
               </Col>
           </Row>
       </Container>
   )
}
 
export default ProductPage

HomePage.js

import React, { useContext, useEffect }  from 'react'
import { ShopContext } from '../context/shopContext'
import { Text, Div, Row, Col, Container } from "atomize";
import { Link } from 'react-router-dom'
import Loading from '../components/Loading'
const HomePage = () => {
   const {fetchAllProducts, products} = useContext(ShopContext)
 
   useEffect(() => {
       fetchAllProducts()
       return () => {
           // cleanup
       };
   }, [fetchAllProducts])
 
   if (!products) return <Loading />
   return (
       <Container>
           <Row>
               {products.map(product => (
                   <Col key={product.id} size="3" >
                       <Link to={`/product/${product.id}`} style={{ textDecoration: 'none' }}>
                           <Div p="2rem">
                               <Div
                                   h="20rem"
                                   bgImg={product.images[0].src}
                                   bgSize="cover"
                                   bgPos="center center"
                                   shadow="3"
                                   hoverShadow="4"
                                   transition="0.3s"
                                   m={{ b: "1.5rem" }}
                                   >
                               </Div>
                               <Text tag="h1" textWeight="300" textSize="subheader" textDecor="none" textColor="black500">{product.title}</Text>
                               <Text tag="h2" textWeight="300" textSize="body" textDecor="none" textColor="gray500">${product.variants[0].price}</Text>
                           </Div>
                       </Link>
                       </Col>
               ))}
           </Row>
       </Container>
   )
}
 
export default HomePage

In both of the pages, we are getting the data from context and rendering it on the frontend by using the map(). We have completed the major part of our store, we are left with smaller components that are used in these pages.
Let us create Loading.js, Cart.js and Navbar.js in the components folder.

Navbar.js

import React, {useContext} from 'react'
import { Container, Anchor, Icon } from 'atomize'
import { Link } from 'react-router-dom'
import { ShopContext } from '../context/shopContext'
 
const Navbar = () => {
 
   const { openCart } = useContext(ShopContext)
 
   return (
       <> 
           <Container d="flex" flexDir="row" p="2rem" justify="space-between" >
               <Link to="/"><Icon name="Store" size="30px" color="black500" /></Link>
               <Anchor onClick={() => openCart()}><Icon name="Bag" size="20px" color="black500" /></Anchor>
           </Container>
       </>
   )
}
 
export default Navbar

Loading.js

import React from "react";
import { Div, Icon } from "atomize";
 
const Loading = () => {
 return (
   <Div
     bg="transparent"
     d="flex"
     align="center"
     justify="center"
     pos="absolute"
     top="0"
     bottom="0"
     right="0"
     left="0"
     style={{ zIndex: -1 }}
   >
     <Icon name="Loading3" size="4rem" color="brand500" />
   </Div>
 );
};
 
 
export default Loading;

Checkout.js

import React, { useContext } from 'react'
import { Div, SideDrawer, Text, Row, Col, Anchor, Button, Container, Icon } from "atomize";
import {ShopContext} from '../context/shopContext'
 
const Cart = () => {
 
   const { isCartOpen, closeCart, checkout } = useContext(ShopContext)
 
   if (checkout.lineItems) {
       return (
           <SideDrawer isOpen={isCartOpen} onClose={closeCart}>
               <Container d="flex" flexDir="column" h="100%">
                   <Row justify="space-between" border={{ b: '1px solid' }} p="0.7rem" borderColor="gray300">
                       <Text tag="h1" textColor="black500" textSize="paragraph" hoverTextColor="black700" transition="0.3s">Bag</Text>
                       <Anchor onClick={() => closeCart()} ><Icon name="Cross" color="black500"/></Anchor>
                   </Row>
                   <Row flexGrow="2" p="0.7rem" overflow="auto" flexWrap="nowrap" flexDir="column">
                       {checkout.lineItems.length < 1 ?
                           <Row>
                               <Col><Text tag="h1" textColor="black500" textSize="paragraph" hoverTextColor="black700" transition="0.3s">Cart Is Empty</Text></Col>
                           </Row>
                           :
                           <>
                               {checkout.lineItems && checkout.lineItems.map(item => (
                                   <Row key={item.id} p={{ t:"5px" }}>
                                       <Col>
                                           <Div bgImg={item.variant.image.src} bgSize="cover" bgPos="center" h="5rem" w="4rem"/>
                                       </Col>
                                       <Col>
                                                         <Text>{item.title}</Text>
                                           <Text>{item.variant.title}</Text>
                                           <Text>{item.quantity}</Text>
                                       </Col>
                                       <Col>
                                           <Text>{item.variant.price}</Text>
                                       </Col>
                                   </Row>
                               ))}
                           </>
                       }
                   </Row>
                   <Row border={{ t: '1px solid' }} p="0.7rem" borderColor="gray300">
                       <Anchor w="100%" href={checkout.webUrl} target="_blank" rel="noopener noreferrer">
                           <Button w="100%" rounded="0" bg="black500" shadow="2" hoverShadow="3" m={{t: '1rem'}}>
                               Checkout
                           </Button>
                       </Anchor>
                   </Row>
               </Container>
           </SideDrawer>
       )
   }
 
   return null
 
}
 
export default Cart

The cart object is returned by shopify in the context and we use it to map items for the frontend. Finally, we import all of these files and use it in our App.js

import React from 'react';
import './App.css';
import { Provider as StyletronProvider, DebugEngine } from "styletron-react";
import { Client as Styletron } from "styletron-engine-atomic";
import { BrowserRouter as Router, Switch, Route, } from "react-router-dom";
import ShopProvider from './context/shopContext'
 
import HomePage from './pages/HomePage'
import ProductPage from './pages/ProductPage'
import Navbar from './components/Navbar'
import Cart from './components/Cart'
 
const debug = process.env.NODE_ENV === "production" ? void 0 : new DebugEngine();
const engine = new Styletron();
 
 
const App = () => {
 return (
   <ShopProvider>
     <StyletronProvider value={engine} debug={debug} debugAfterHydration>
       <Router>
         <Navbar />
         <Cart />
         <Switch>
           <Route path="/product/:id">
             <ProductPage />
           </Route>
           <Route path="/">
             <HomePage />
           </Route>
         </Switch>
       </Router>
     </StyletronProvider>
   </ShopProvider>
 );
}
 
export default App;

Here, we have two main routes for our pages that are dynamic based on their ids. For styling purposes, we have wrapped the components with the StyletronProvider which is our style engine for the project. And at the end, the whole app is wrapped by the ShopProvider for accessing the global state.

Let us run the project with the following command and see the result.

yarn start

Homepage

Cart

Clicking on checkout will lead to a payment page on Shopify.

In this article, we have seen how we can create your own eCommerce website with react and Shopify in a few steps. The whole process is really simple and you are free to add as much customization as you like. We hope this tutorial must have provided you with enough knowledge to start more on your own.

What’s the biggest thing you’re struggling with right now that we as a technology consulting company can help you with? Feel free to reach out to us at info@jalantechnologies.com. We hope our assistance will help!

Tags

JTC

Like puppies, we build software that everyone love

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.