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 built-in functions 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
Clicking on checkout will lead to a payment page on Shopify. Your Ecommerce Website with shopify has been created
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 your own Ecommerce stores.
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 Jalan Technologies. We hope our assistance will help!