E-commerce Project (React)

This is a react app that is part of John's Udemy course

This is a very big app and hence making notes of what we learn and also entire flow of the app here.

πŸ“š Things we can learn

1. Styled components intro

import React from 'react'
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
import styled from 'styled-components'
import { Navbar, Sidebar, Footer } from './components'

// THIS IS HOW WE DEFINE STYLED COMPONENTS
const Button = styled.button`
  background: green;
  color: white;
`

function App() {
  return (
    <div>
      <h4>comfy sloth starter</h4>
      <Button>Hello</Button>
    </div>
  )
}

export default App

No name collisions in styled components that is the advantage, meaning, we can nest the styles like this

2. Service to send subscription emails

Let's say we have a form in our website and we need to send some kind of subscription emails to let them know about the promotions we have. You can use a service like https://formspree.io/arrow-up-right or https://mailchimp.com/en-ca/?currency=CADarrow-up-right

To know more, visit udemy react course from John - video # 437 - Formspree. He uses this kind of service for his website to give monthly promotions through email

3. How to format price

Intl function handles all the currency conversion for us. Just pass the cents and it gives you back dollars with proper currency symbol $ at the beginning. For more info refer E-commerce Project (React)

4. useHistory hook

To programmatically navigate back to some page, we can use this hook. Refer to E-commerce Project (React) that shows the usage of useHistory. An alternative approach to this is

Later we will use this too in our app when using auth0

Later we will use this too in our app when using auth0 and converting our project to react-router-6

πŸ„β€β™€οΈ Flow of the app

For each step, if we are modifying any files I will mention that at the beginning of that step (blue color hint). Green color hint button will be sometimes used at the end of some step to describe what functionality should be working at that particular step. If there is any complex things that happens in any step we will add that to Things we can learn section above and I will explain that in detail

1. Starter Project

Can be found here https://github.com/sandeep194920/React_MUI_Express_Projects/tree/25_ecommerce_apparrow-up-right

commit ID - a67783a5c95c19a5ba74c50643c4d8130e672f55

2. React router - Add all routes

circle-info

25_ecommerce_app/src/pages/index.js

25_ecommerce_app/src/App.js

Let's add react router to display different routes like Home page, About page, Products page, Single Product page, Cart, Checkout. We will also use Pages/index.js to import all Pages into index page as usual.

Just to brush your memory, this is how we add routes in react-router-5. Taking an example here to show products

and the single-product route will be like this

We need to have Navbar, Sidebar and Footer in all the pages so we add it like this. Technically, we can place Sidebar in any order(as it is position fixed) but let's just put it after Navbar

You can test the app at this point. Navigate to /products, /products/xyz, /somethingelse and it should show proper pages with headers and footers in each page (Just the text in each page)

Full code below

3. Navbar setup

circle-info

25_ecommerce_app/src/components/Navbar.js

Let's now setup Navbar

In Navbar, one thing to notice is the logo image we are getting from assets. We import like this

Currently the code in Navbar looks like this

Also, in Navbar we are placing our logo image inside react-router's Link

For styles, we have this min-width just for you to remember

The app looks like this now in big and small screens

Full code looks like this

3. Navbar - Cart buttons (not functionality yet)

circle-info

25_ecommerce_app/src/components/Navbar.js

25_ecommerce_app/src/components/CartButtons.js

In previous step, we left off with adding cart buttons to Navbar. Let's add them now.

We are just adding Cart button Link and Logout button for now. No functionality yet.

Full code

circle-info

25_ecommerce_app/src/components/Footer.js

Now that we have Navbar setup (no functionality yet however), we will setup footer now.

Fullcode

5. Sidebar

circle-info

25_ecommerce_app/src/components/Sidebar.js

Let's now design the sidebar. Checkout and cart button will be conditionally shown (Even logout).

For toggling the sidebar we always do this. We add have one class with z-index -1 and we translate to push it to left so it wont be shown. Then to show we have a class and to hide we can remove that class.

Full code

6. Sidebar toggle functionality implemented in Product context

circle-info

25_ecommerce_app/src/context/products_context.js - Define open and close functions for sidebar

25_ecommerce_app/src/reducers/products_reducer.js - Define action handlers for the above functions in context

25_ecommerce_app/src/index.js - Wrap the app inside the above product context

25_ecommerce_app/src/components/Sidebar.js - Invoke the function to close sidebar from products_context

25_ecommerce_app/src/components/CartButtons.js - Invoke the function to close sidebar from products_context

25_ecommerce_app/src/components/Navbar.js - Invoke the function to open sidebar from products_context

In our previous step 5, we did sidebar. The things to note now is,

  • The button we click to close the sidebar will be on sidebar

  • Note that the button we click to open the sidebar will be on the Navbar. Hence we need contact from Navbar to sidebar. So we could,

    • Either define a state in App.js and define sidebar open and close functionality and then pass this as prop to sidebar and navbar. The problem here is prop drilling and also app.js grows quite big very fast

    • Alternatively in a cleaner way, we could define a context to do this which we will do it now in the product context. Each context will have contact with it's reducer so the dispatched action from Product context will be inside Products reducer.

Why are we adding the sidebar open and close functionality in productContext? Didn't we find a better place to add this?

Well honestly we could have created one more context just to handle this functionality, but I feel, since productContext is a bit smaller whose primary responsibility is to get products, we can just add the sidebar functionality here.

Since we are now working in Product Context, in order to access the values from this context we need to wrap our app in the ProductProvider, so let's do that first

Full Code for index.js

Since it's a very big app, instead of useState, we will use useReducer in the context to manage most of the state.

Full code

circle-check
Click this to open sidebar
close sidebar by clicking any of these
circle-exclamation

7. Error page

circle-info

25_ecommerce_app/src/pages/ErrorPage.js

Our intention is to design the error page like this

This would be the global css that is in index.css that is used in ErrorPage below

And also this should be the css for the btn that comes from index.css that is used in ErrorPage below and many other places

Full code

8. About and Checkout Page

circle-info

25_ecommerce_app/src/pages/AboutPage.js

25_ecommerce_app/src/pages/CheckoutPage.js

25_ecommerce_app/src/components/PageHero.js

Let's design the below pages for /about and /checkout. Each of these pages also contain a hero section for heading of that page.

Full code

9. Home page

circle-info

25_ecommerce_app/src/pages/HomePage.js

Below components are used inside HomePage above

25_ecommerce_app/src/components/Hero.js

25_ecommerce_app/src/components/FeaturedProducts.js - Not touching this now (just adding it here so you know that this belongs to Home page and needs to be modified later)

25_ecommerce_app/src/components/Services.js

25_ecommerce_app/src/components/Contact.js

Let's now design Home page.

Note that: We will skip the Featured Products section for now in Home Page and we will come back to this later. You don't need to worry about this now.

One thing to note here for Services section. It basically looks like this in home page

Even though we can hardcode these icons and text here (marked in blue arrows in the above image), it would still be better to get the icons and text from a file called /utils/constants. The {services} file looks like this

Note that in in export const services, in each item we have an icon that is imported from react icons. In order for the icons to be placed here, we need to import react. For export const links, we didn't need this as we are not using any jsx like we do for icons in services. This is just something for you to keep in mind while using jsx in non component file. By non-component file, I mean the file which is not a react component. So the rule of thumb is, if you are using jsx in any file even a small bit like this icon then you need to import react.

Full code

10. API Info for this project

We will be using John's API built in Node course which even I have built, but still I want to use John's API to keep my app up and running always.

In the node course we built a full blown e-commerce api with some of these routes

  • CRUD - products - where admin could create, update and delete products. But read products was a public get method and no auth was required

  • CRUD - users

In our app now, we will only use the read functionality of products route which is open to public like any other API we used in our other projects. We have placed our products API URL in 25_ecommerce_app/src/utils/constants.js

Some of the products will have feature:true and other products that don't have this will be featured false. The featured products will be displayed on HomePage. All the products will be displayed on /products page like this. We will get the products and show them both in Home and Products page in products context.

Later we will filter, sort and everything, but for now in next step, let's just fetch the products in products context.

circle-info

25_ecommerce_app/src/context/products_context.js

25_ecommerce_app/src/reducers/products_reducer.js

It's good that we are able to fetch products. But we now want to handle loading, error and all that before displaying the products to UI. Let's do that now by adding more properties in iniitalState of products_context

Full code

At this point, you could see the react-dev-tools and this is what you should see

circle-info

25_ecommerce_app/src/components/FeaturedProducts.js

All the below components are used in FeaturedProducts component above

25_ecommerce_app/src/components/Loading.js

25_ecommerce_app/src/components/Error.js

25_ecommerce_app/src/components/Product.js

Ok, now that we are fetching the products from API, let's add in Featured products to home page that we had left off in E-commerce Project (React)

Full code

13. Format Price

circle-info

25_ecommerce_app/src/utils/helpers.js

25_ecommerce_app/src/components/Product.js

Ok, now that we have displayed the featured products on home page, if you take a look at the image in previous step, we are showing numbers (cents) for price without any format. We need to work on that now.

You need to remember these things when working with price (dollars and cents) in javascript

  • When we look at payment processors like stripe, they will be looking for the smallest unit of currency (in cents). For example, if we are selling in dollars they will be looking that dollar converted into cents (smallest possible unit of that dollar)

  • The second reason is, in our app we will be building the cart. In that cart, we will have a bunch of calculations like converting in to decimals and so on. The problem with javascript is, during handling of decimals, once in a while we get some weird values. We don't want to have any kind of bug when it comes to real money.

So the takeaway is, always setup the amount in smallest possible unit. Technically we can just divide our cents and we get amount in dollars. But the problem is with decimals some times that like I said, JS will not work well in some cases for decimals. To deal with that we will setup a util function to make this formatting which converts cents (smallest unit of currency) to dollar representation. The advantage of this is, JS will provide internationalization option where we can also convert to different country formatting if we want to.

So let's do that util function now.

Full code

14. Single Product Functionality Part 1

circle-info

25_ecommerce_app/src/context/products_context.js

25_ecommerce_app/src/reducers/products_reducer.js

If we click on a Product link on one of Featured product in home page it opens a single product page and it looks like this currently

We will implement the fetch functionality for single product in products context like we did while fetching the products. One difference is, we called the fetchProducts() inside useEffect of products_context, but we will call fetchSingleProduct inside useEffect of SingleProduct component as there is no need to fetch it until we reach SingleProduct page.

In this step we will write code for context and reducer. In next part we will then call that fetchSingleProduct function inside the SingleProduct component

Full code

15. Single Product Functionality Part 2

circle-info

25_ecommerce_app/src/pages/SingleProductPage.js

All the below components are in SingleProductPage above

25_ecommerce_app/src/components/PageHero.js

25_ecommerce_app/src/components/ProductImages.js

25_ecommerce_app/src/components/Stars.js

Since SingleProductPage is big and has lot of parts, let's divide SingleProductPage into multiple components like this and get these components into SingleProductPage as shown below. Also we need to work a little bit to change Page hero as it has an extra text to display like this -> Home / Products / Suede Armchair

In App.js we have this code to display SingleProduct

After adding the useEffect inside SingleProduct to fetch single product it looks this way

When we click on any product (click on one of the feature products on home page) console log in line 15 gives this

Now we will also need to check for loading and error and handle them inside SingleProduct before we show single product on screen. It looks like this after adding loading and error

Now change the single_product_url and it should show you the error like this

Now we can leave it at this, but a better user experience would be to automatically navigate to home screen after 3 seconds if there is an error. Let's do it using another useEffect

Our code currently looks this way

And the singleProduct page (due to code above) looks like this

Next, let's work on Product images. Before you code Product Images, let me show you the change we made on PageHero

After adding conditional code to display hero on single product page (which is different on other pages)

Product Images

Let's now work on Product Images part.

We will pass the images to <ProductImages/> and then use s simple useState to show current selected image (no need to use context since it is local to ProductImage component)

Full Code for ProductImages

Stars

Let's now do Stars component. We will first take manual approach. Then later take Programmatic approach.

Stars - Manual Approach

Here we set each star like this. So we set this five times.

The above is a single star

Let's add five stars the same way

Stars - Programmatic Approach

Above, we took an approach of repeating the same code five times for five stars. Let's now take programmatic approach.

To better understand this refer my stackoverfow answer https://stackoverflow.com/a/68029192/10824697arrow-up-right

Full code for Stars

16. Single Product Cart

circle-info

25_ecommerce_app/src/pages/SingleProductPage.js

25_ecommerce_app/src/components/AddToCart.js

25_ecommerce_app/src/components/AmountButtons.js

We have to do this cart functionality where we need to have

  • Colors for the products

  • Add / Remove from cart

  • And while adding to cart we also need to check if those many items exist in the cart

Color

After adding color selection, this is how it looks

At this point after adding colors the AddToCart looks like this

Cart Buttons

Let's now add cart increase and decrease functionality

Full Code

The SingleProductPage looks this way till now

17. Filter context in Product Page

circle-info

25_ecommerce_app/src/context/filter_context.js

25_ecommerce_app/src/reducers/filter_reducer.js

25_ecommerce_app/src/index.js -> Wrap App with FilterContext Provider

NOT ADDING UI BUT JUST THE FUNCTIONALITY OF FILTERING HERE

circle-exclamation
circle-exclamation

So this is the idea we are going to focus on

  • We loaded products in products context initially like this

  • Now in the below image you can see that we can filter the products and the products will narrow down based on the filter. Once we click on clear filters, the actual products need to be shown. How do we do this?

  • Let's say we have products array that we get from initial load which is in product_context as shown above in first bullet point.

  • We need to get that products from product_context to filter context somehow (which I will explain in a moment below)

  • In the filtered context we now will have products (all products).

  • But what if user applies different filters, then the products change, and then when user clicks on clear filter we can't get back original products we loaded above (all_products)

  • For this reason, we should not apply filters on all_products, but instead make a copy(using spread operator so we dont alter original array) of this all_products inside filter_context and then apply filters on that products.

Ok too much info above. Let's break this into simple steps and work on them

  • We will see how to get products from products_context into filtered_context and set that products in filtered context

  • In filtered_context, we will set two state (reducer) values, one for all_products and one for filtered_products. Both will be same initially (but remember to use spread operator to make copies of products so we don't modify same products reference. If we do that then we technically alter original products and we don't want that. That means filtered_products will alter original products which is bad and we can't get back original products)

Ok so let's implement this and then understand while implementing

Here's my filter_context initially. I define the initial state that has filtered_products and all_products (initially they will be set to same) that we get from products_context

Now how do we get the products from products_context to sset into filtered_products and all_products. Here's how we get

Now how do we wrap our app with this Filter Provider? currently this is how index looks

This is the error you get if you set it up as above

The above error is because you are importing products from products context into filter context with the Filter Provider being the parent. If Product Provider is the parent then this will work.

circle-exclamation

Let's do the right setup now

This would now solve the error and filter_context now gets the products from products context

filter_reducer looks like this

At this point to test this, we can make use of react dev tools

Full Code

18. Products page - Grid View and List View of Products List

circle-info

25_ecommerce_app/src/pages/ProductsPage.js

25_ecommerce_app/src/components/ProductList.js

25_ecommerce_app/src/context/filter_context.js

25_ecommerce_app/src/components/GridView.js

25_ecommerce_app/src/components/ListView.js

Let's now work in Products page to show products on the screen. This is what we need to design.

To start with, the above code looks like this and the page looks as below

In ProductsList component, we return 3 things

  • Grid View of products

  • List View of products

  • When the filters don't match then we need to display "Sorry, no match" text

Let's get the filteredProducts from filter_context.

The productsList currently looks like this

The ProductsList code looks like this

Now let's say we also want to have list view. We need some kind of state value for now to control this. We will later add buttons to control this. For now lets add this state value in 25_ecommerce_app/src/context/filter_context.js

And we use it in ProductsList like this

Currently it looks like this since the ListView is not yet built

After adding the List View code (you can refer the final code of it just below), it looks like this

Full Code

19. Sort UI - Products Page

circle-info

25_ecommerce_app/src/components/Sort.js

Now that we have List View and Grid View of products, lets work on showing sort UI next.

expected sort UI

You can change the grid_view to false in filter_context and then you will have list view as you know. But now let's focus on Sort UI on the ProductsPage. In the next step we will work on sort functionality

Full code

20. Sort Functionality - Products Page

circle-info

25_ecommerce_app/src/context/filter_context.js

25_ecommerce_app/src/reducers/filter_reducer.js

Grid View and List View toggle buttons

Let's first make the Grid View and List View buttons work. To make it work, currently we have to switch grid_view state to true or false manually in filter_context. Let's add a function to make it work in filter_context

Sort functionality - Controlled inputs for Select

Let's have a state value called sort and once we change the select option, that state value changes.

Now we have a state for changing the sort dropdown. Once we change that state by clicking the drop-down and selecting a value, then we need to run a useEffect and then sort the products accordingly and also set the sorted products to be the new products.

Let's define that useEffect in filter_context

reducer for this SORT_PRODUCTS looks this way

Now all 4 sort functionalities work as expected.

Full code

21. Filters - Product Page

circle-info

25_ecommerce_app/src/context/filter_context.js

25_ecommerce_app/src/reducers/filter_reducer.js

25_ecommerce_app/src/components/Filters.js

We completed sorting, let's now work on the left part filters.

We will set the filters as controlled inputs. First let's define them in filter_context initial state as an object as we will have multiple values and we would change only one

Notice that max_price is not the random price. It is actually the price of the highest priced product. In order for this to be the highest priced product, we need to set the max_price when we actually dispatch the products. Let's do that in filter_reducer.

Now that we have added the state, let's add filter UI and also a function to handle that. All the filter options will be controlled inputs (a single function will handle that)

This is what our Filter component looks currently

For this now our updateFilters looks like this

And for this the filter reducer looks like this

Now at this point as the search changes the state value gets set for that search which is text inside state.filters

Now we need to run a useEffect like we did for sort where, once the search updates we need to get the products related to that search, so lets do that in filter_context

Along with previously written useEffect of sort it looks this way

Full Code

21. Sort UI + Functionality continued - Products Page

circle-info

25_ecommerce_app/src/components/Filters.js

25_ecommerce_app/src/utils/helpers.js

Let's now design Categories functionality in filters. We have pass all the data to a getUniqueValues and then get the unique categories

Categories Filter

Let's now map these cateogries, colors and companies and display as filters in the UI

This above code leads to this below UI

Let's add active class and stuff

Now look at onClick function which is updateFilters. That gets e.target.name and e.target.value from the button. The e.target.name will be category but the e.target.value will be undefined as the button will not have a e.target.value unlike input. To solve this we can make use of e.target.textContent. So the updateFilters in filter_context would look like this

Companies Filter

Similar to sort functionality we did where we use select and options

Colors Filter

Now if you observe, we need to pass the e.target.value to updateFilter function like we did in text input. Since that was not possible in button for companies we used e.target.textContent. Now here in colors, both e.target.value and e.target.textContent both are not possible. Hence In this filter we will use data-color (data-set html) property.

circle-check

So the color functionality looks this way

Price Filter

Let's now work on the Price filter

Shipping filter

Clear Filters button

Now that we have done adding all the filters lets add a clear filter button

Last updated

Was this helpful?