# E-commerce Project (React)

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

## 📚 Things we can learn

### 1. Styled components intro

```javascript
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
```

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FuUzmX359uqFFuBpiVG9V%2Fimage.png?alt=media&#x26;token=c8f27f15-b6b8-4cb0-a3f4-a02ae2bc1823" alt=""><figcaption></figcaption></figure>

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FsfyKtY1yE6rRcimqVJvw%2Fimage.png?alt=media&#x26;token=59c25764-38b5-41e6-b7a3-bb16b1d18035" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FEN231txrMLNHIKa0OE7e%2Fimage.png?alt=media&#x26;token=a579859f-6931-4c2c-9888-baac05a3d10a" alt=""><figcaption></figcaption></figure>

### 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/> or <https://mailchimp.com/en-ca/?currency=CAD>

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2F5VpiUkpKyJECHupZSibu%2Fimage.png?alt=media&#x26;token=d3106a29-90d1-4aba-acbc-426295469048" alt=""><figcaption></figcaption></figure>

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&#x20;

`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 [#13.-format-price](#13.-format-price "mention")

```javascript
export const formatPrice = (number) => {
  const formattedNumber = new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD',
  }).format(number / 100) // note that we still need to divide by 100 but don't have to format the decimals
  return formattedNumber // it automatically adds the currency representation (Adds $ at the beginning)
}

export const getUniqueValues = () => {}

```

### 4. useHistory hook&#x20;

&#x20;To programmatically navigate back to some page, we can use this hook. Refer to [#15.-single-product-functionality-part-2](#15.-single-product-functionality-part-2 "mention") that shows the usage of useHistory. An alternative approach to this is&#x20;

* to use Redirect (react router 5) like this - <https://github.com/sandeep194920/React_MUI_Express_Projects/tree/master/24_github_users#react-router-render-prop-and-redirect---an-alternative-way-to-display-underlying-route-especially-for-protected-route>

Later we will use this too in our app when using auth0&#x20;

* to use Navigate in (react router 6) like this - <https://github.com/sandeep194920/React_MUI_Express_Projects/tree/master/24_github_users#37-lets-convert-react-router-5-to-react-router-6>

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_app>

**commit ID** - `a67783a5c95c19a5ba74c50643c4d8130e672f55`

### 2. React router - Add all routes

{% hint style="info" %}
25\_ecommerce\_app/src/pages/index.js

25\_ecommerce\_app/src/App.js
{% endhint %}

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.

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2Fe1A9rD7DmZgw7nrQwPhh%2Fimage.png?alt=media&#x26;token=9d25a01f-d44f-4987-bd10-0114d0c58fb9" alt=""><figcaption></figcaption></figure>

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

```javascript
{/* Products */}
<Route exact path="/products">
  <Products />
</Route>
```

and the single-product route will be like this

```javascript
{/* SingleProduct -> This would be little different as it 
would have a children prop*/}
<Route exact path="/products/:id" children={<SingleProduct />} />
```

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

```javascript
  return (
    <Router>
      {/* Display Navbar & Sidebar in all pages, so it's placed outside of <Switch/> */}
      <Navbar />
      <Sidebar /> {/*Sidebar is position fixed so can be put in any order*/}
      <Switch>
        {/* Home */}
        <Route exact path="/">
          <Home />
        </Route>

        {/* About */}
        <Route exact path="/about">
          <About />
        </Route>

        {/* Cart */}
        <Route exact path="/cart">
          <Cart />
        </Route>

        {/* Products */}
        <Route exact path="/products">
          <Products />
        </Route>

        {/* SingleProduct -> This would be little different as it would have a children prop*/}
        <Route exact path="/products/:id" children={<SingleProduct />} />

        {/* Checkout -> This would be wrapped in PrivateRoute later */}
        <Route exact path="/checkout">
          <Checkout />
        </Route>

        {/* Error */}
        <Route exact path="*">
          <Error />
        </Route>
      </Switch>
      {/* Display footer in all pages, so it's placed outside of <Switch/> */}
      <Footer />
    </Router>
  )
```

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**
>
> {% code title="pages/index.js" %}
>
> ```javascript
> import About from './AboutPage'
> import AuthWrapper from './AuthWrapper'
> import Cart from './CartPage'
> import Checkout from './CheckoutPage'
> import Error from './ErrorPage'
> import Home from './HomePage'
> import PrivateRoute from './PrivateRoute'
> import Products from './ProductsPage'
> import SingleProduct from './SingleProductPage'
>
> export {
>   About,
>   AuthWrapper,
>   Cart,
>   Checkout,
>   Error,
>   Home,
>   PrivateRoute,
>   Products,
>   SingleProduct,
> }
> ```
>
> {% endcode %}
>
> {% code title="App.js" %}
>
> ```javascript
> import React from 'react'
> import { BrowserRouter as Router, Switch, Route } from 'react-router-dom'
> import { Navbar, Sidebar, Footer } from './components'
> import {
>   About,
>   AuthWrapper,
>   Cart,
>   Checkout,
>   Error,
>   Home,
>   PrivateRoute,
>   Products,
>   SingleProduct,
> } from './pages'
>
> function App() {
>   return (
>     <Router>
>       {/* Display Navbar & Sidebar in all pages, so it's placed outside of <Switch/> */}
>       <Navbar />
>       <Sidebar /> {/*Sidebar is position fixed so can be put in any order*/}
>       <Switch>
>         {/* Home */}
>         <Route exact path="/">
>           <Home />
>         </Route>
>
>         {/* About */}
>         <Route exact path="/about">
>           <About />
>         </Route>
>
>         {/* Cart */}
>         <Route exact path="/cart">
>           <Cart />
>         </Route>
>
>         {/* Products */}
>         <Route exact path="/products">
>           <Products />
>         </Route>
>
>         {/* SingleProduct -> This would be little different as it would have a children prop*/}
>         <Route exact path="/products/:id" children={<SingleProduct />} />
>
>         {/* Checkout -> This would be wrapped in PrivateRoute later */}
>         <Route exact path="/checkout">
>           <Checkout />
>         </Route>
>
>         {/* Error */}
>         <Route exact path="*">
>           <Error />
>         </Route>
>       </Switch>
>       {/* Display footer in all pages, so it's placed outside of <Switch/> */}
>       <Footer />
>     </Router>
>   )
> }
>
> export default App
>
> ```
>
> {% endcode %}

#### 3. Navbar setup

{% hint style="info" %}
25\_ecommerce\_app/src/components/Navbar.js
{% endhint %}

Let's now setup Navbar

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

```javascript
import logo from '../assets/logo.svg'
```

Currently the code in Navbar looks like this&#x20;

```javascript
import React from 'react'
import styled from 'styled-components'
import logo from '../assets/logo.svg'
import { FaBars } from 'react-icons/fa'
import { Link } from 'react-router-dom'
import { links } from '../utils/constants'
import CartButtons from './CartButtons'
import { useProductsContext } from '../context/products_context'
import { useUserContext } from '../context/user_context'

const Nav = () => {
  return <h4>navbar</h4>
}

const NavContainer = styled.nav`
  height: 5rem;
  display: flex;
  align-items: center;
  justify-content: center;

  .nav-center {
    width: 90vw;
    margin: 0 auto;
    max-width: var(--max-width);
  }
  .nav-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    img {
      width: 175px;
      margin-left: -15px;
    }
  }
  .nav-toggle {
    background: transparent;
    border: transparent;
    color: var(--clr-primary-5);
    cursor: pointer;
    svg {
      font-size: 2rem;
    }
  }
  .nav-links {
    display: none;
  }
  .cart-btn-wrapper {
    display: none;
  }
  @media (min-width: 992px) {
    .nav-toggle {
      display: none;
    }
    .nav-center {
      display: grid;
      grid-template-columns: auto 1fr auto;
      align-items: center;
    }
    .nav-links {
      display: flex;
      justify-content: center;
      li {
        margin: 0 0.5rem;
      }
      a {
        color: var(--clr-grey-3);
        font-size: 1rem;
        text-transform: capitalize;
        letter-spacing: var(--spacing);
        padding: 0.5rem;
        &:hover {
          border-bottom: 2px solid var(--clr-primary-7);
        }
      }
    }
    .cart-btn-wrapper {
      display: grid;
    }
  }
`

export default Nav

```

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

```javascript
<Link to="/">
  <img src={logo} alt="comfy sloth" />
</Link>
```

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

```javascript
  // nav-toggle will be hidden on small screen and will be display only if we have a minimum of 992px wide screen
  @media (min-width: 992px) {
    .nav-toggle {
      display: none;
    }
```

The app looks like this now in big and small screens

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FFUda7TuhKTsvcUD9kM5t%2Fimage.png?alt=media&#x26;token=0d0f0528-32cf-4d4d-bd7e-071426d8bb48" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2F5teOu7ZD4CuvjRjFgcUY%2Fimage.png?alt=media&#x26;token=cdb96a97-81c8-4101-b422-d515eb8f3304" alt=""><figcaption></figcaption></figure>

> Full code looks like this
>
> {% code title="components/Navbar.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import logo from '../assets/logo.svg'
> import { FaBars } from 'react-icons/fa'
> import { Link } from 'react-router-dom'
> import { links } from '../utils/constants'
> import CartButtons from './CartButtons'
> import { useProductsContext } from '../context/products_context'
> import { useUserContext } from '../context/user_context'
>
> const Nav = () => {
>   return (
>     <NavContainer>
>       <div className="nav-center">
>         <div className="nav-header">
>           <Link to="/">
>             <img src={logo} alt="comfy sloth" />
>           </Link>
>           <button type="button" className="nav-toggle">
>             <FaBars />
>           </button>
>         </div>
>         {/* nav-links will also be re-used in Sidebar */}
>         <ul className="nav-links">
>           {links.map((link) => {
>             const { id, text, url } = link
>             return (
>               <li key={id}>
>                 <Link to={url}>{text}</Link>
>               </li>
>             )
>           })}
>         </ul>
>         {/* We will add CartButtons here shortly */}
>       </div>
>     </NavContainer>
>   )
> }
>
> const NavContainer = styled.nav`
>   height: 5rem;
>   display: flex;
>   align-items: center;
>   justify-content: center;
>
>   .nav-center {
>     width: 90vw;
>     margin: 0 auto;
>     max-width: var(--max-width);
>   }
>   .nav-header {
>     display: flex;
>     align-items: center;
>     justify-content: space-between;
>     img {
>       width: 175px;
>       margin-left: -15px;
>     }
>   }
>   .nav-toggle {
>     background: transparent;
>     border: transparent;
>     color: var(--clr-primary-5);
>     cursor: pointer;
>     svg {
>       font-size: 2rem;
>     }
>   }
>   .nav-links {
>     display: none;
>   }
>   .cart-btn-wrapper {
>     display: none;
>   }
>   // nav-toggle will be hidden on small screen and will be display only if we have a minimum of 992px wide screen
>   @media (min-width: 992px) {
>     .nav-toggle {
>       display: none;
>     }
>     .nav-center {
>       display: grid;
>       grid-template-columns: auto 1fr auto;
>       align-items: center;
>     }
>     .nav-links {
>       display: flex;
>       justify-content: center;
>       li {
>         margin: 0 0.5rem;
>       }
>       a {
>         color: var(--clr-grey-3);
>         font-size: 1rem;
>         text-transform: capitalize;
>         letter-spacing: var(--spacing);
>         padding: 0.5rem;
>         &:hover {
>           border-bottom: 2px solid var(--clr-primary-7);
>         }
>       }
>     }
>     .cart-btn-wrapper {
>       display: grid;
>     }
>   }
> `
>
> export default Nav
>
> ```
>
> {% endcode %}

### 3. Navbar - Cart buttons (not functionality yet)

{% hint style="info" %}
25\_ecommerce\_app/src/components/Navbar.js

25\_ecommerce\_app/src/components/CartButtons.js
{% endhint %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FAKYxadi3MTai3nkIqbZD%2Fimage.png?alt=media&#x26;token=49be0477-d22a-4776-a2a8-573abb8ad027" alt=""><figcaption></figcaption></figure>

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

> Full code&#x20;
>
> {% code title="/components/Navbar.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import logo from '../assets/logo.svg'
> import { FaBars } from 'react-icons/fa'
> import { Link } from 'react-router-dom'
> import { links } from '../utils/constants'
> import CartButtons from './CartButtons'
> import { useProductsContext } from '../context/products_context'
> import { useUserContext } from '../context/user_context'
>
> const Nav = () => {
>   return (
>     <NavContainer>
>       <div className="nav-center">
>         <div className="nav-header">
>           <Link to="/">
>             <img src={logo} alt="comfy sloth" />
>           </Link>
>           <button type="button" className="nav-toggle">
>             <FaBars />
>           </button>
>         </div>
>         {/* nav-links will also be re-used in Sidebar */}
>         <ul className="nav-links">
>           {links.map((link) => {
>             const { id, text, url } = link
>             return (
>               <li key={id}>
>                 <Link to={url}>{text}</Link>
>               </li>
>             )
>           })}
>         </ul>
>       
>       
>         <CartButtons />
>       </div>
>     </NavContainer>
>   )
> }
>
> const NavContainer = styled.nav`
>   height: 5rem;
>   display: flex;
>   align-items: center;
>   justify-content: center;
>
>   .nav-center {
>     width: 90vw;
>     margin: 0 auto;
>     max-width: var(--max-width);
>   }
>   .nav-header {
>     display: flex;
>     align-items: center;
>     justify-content: space-between;
>     img {
>       width: 175px;
>       margin-left: -15px;
>     }
>   }
>   .nav-toggle {
>     background: transparent;
>     border: transparent;
>     color: var(--clr-primary-5);
>     cursor: pointer;
>     svg {
>       font-size: 2rem;
>     }
>   }
>   .nav-links {
>     display: none;
>   }
>   .cart-btn-wrapper {
>     display: none;
>   }
>   // nav-toggle will be hidden on small screen and will be display only if we have a minimum of 992px wide screen
>   @media (min-width: 992px) {
>     .nav-toggle {
>       display: none;
>     }
>     .nav-center {
>       display: grid;
>       grid-template-columns: auto 1fr auto;
>       align-items: center;
>     }
>     .nav-links {
>       display: flex;
>       justify-content: center;
>       li {
>         margin: 0 0.5rem;
>       }
>       a {
>         color: var(--clr-grey-3);
>         font-size: 1rem;
>         text-transform: capitalize;
>         letter-spacing: var(--spacing);
>         padding: 0.5rem;
>         &:hover {
>           border-bottom: 2px solid var(--clr-primary-7);
>         }
>       }
>     }
>     .cart-btn-wrapper {
>       display: grid;
>     }
>   }
> `
>
> export default Nav
>
> ```
>
> {% endcode %}
>
> ```javascript
> import React from 'react'
> import { FaShoppingCart, FaUserMinus, FaUserPlus } from 'react-icons/fa'
> import { Link } from 'react-router-dom'
> import styled from 'styled-components'
> import { useProductsContext } from '../context/products_context'
> import { useCartContext } from '../context/cart_context'
> import { useUserContext } from '../context/user_context'
>
> const CartButtons = () => {
>   return (
>     <Wrapper className="cart-btn-wrapper">
>       <Link to="/cart" className="cart-btn">
>         Cart
>         <span className="cart-container">
>           <FaShoppingCart />
>           <span className="cart-value">12</span>
>         </span>
>       </Link>
>       <button type="button" className="auth-btn">
>         Login
>         <FaUserPlus />
>       </button>
>     </Wrapper>
>   )
> }
>
> const Wrapper = styled.div`
>   display: grid;
>   grid-template-columns: 1fr 1fr;
>   align-items: center;
>   width: 225px;
>
>   .cart-btn {
>     color: var(--clr-grey-1);
>     font-size: 1.5rem;
>     letter-spacing: var(--spacing);
>     color: var(--clr-grey-1);
>     display: flex;
>
>     align-items: center;
>   }
>   .cart-container {
>     display: flex;
>     align-items: center;
>     position: relative;
>     svg {
>       height: 1.6rem;
>       margin-left: 5px;
>     }
>   }
>   .cart-value {
>     position: absolute;
>     top: -10px;
>     right: -16px;
>     background: var(--clr-primary-5);
>     width: 16px;
>     height: 16px;
>     display: flex;
>     align-items: center;
>     justify-content: center;
>     border-radius: 50%;
>     font-size: 0.75rem;
>     color: var(--clr-white);
>     padding: 12px;
>   }
>   .auth-btn {
>     display: flex;
>     align-items: center;
>     background: transparent;
>     border-color: transparent;
>     font-size: 1.5rem;
>     cursor: pointer;
>     color: var(--clr-grey-1);
>     letter-spacing: var(--spacing);
>     svg {
>       margin-left: 5px;
>     }
>   }
> `
> export default CartButtons
>
> ```

### 4. Footer&#x20;

{% hint style="info" %}
`25_ecommerce_app/src/components/Footer.js`
{% endhint %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2Fo2quNXsqbuCJgbdUEsEH%2Fimage.png?alt=media&#x26;token=e95e152e-7700-45f8-ad2b-30b2a50e32fa" alt=""><figcaption></figcaption></figure>

> Fullcode
>
> {% code title="components/Footer.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> const Footer = () => {
>   return (
>     <Wrapper>
>       <h5>
>         &copy; {new Date().getFullYear()}
>         <span> ComfySloth</span>
>       </h5>
>       <h5> All rights reserved</h5>
>     </Wrapper>
>   )
> }
>
> const Wrapper = styled.footer`
>   height: 5rem;
>   display: flex;
>   flex-direction: column;
>   justify-content: center;
>   align-items: center;
>   background: var(--clr-black);
>   text-align: center;
>   span {
>     color: var(--clr-primary-5);
>   }
>   h5 {
>     color: var(--clr-white);
>     margin: 0.1rem;
>
>     font-weight: 400;
>     text-transform: none;
>     line-height: 1.25;
>   }
>   @media (min-width: 776px) {
>     flex-direction: row;
>   }
> `
>
> export default Footer
> ```
>
> {% endcode %}

### 5. Sidebar

{% hint style="info" %}
25\_ecommerce\_app/src/components/Sidebar.js
{% endhint %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2F8s2WjtiXa1L4hsFf0ekZ%2Fimage.png?alt=media&#x26;token=91d542c7-1091-4400-9277-444c3a0df1d0" alt=""><figcaption></figcaption></figure>

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.&#x20;

```javascript
  .sidebar {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: var(--clr-white);
    transition: var(--transition);
    transform: translate(-100%);
    z-index: -1;
  }
  // show-sidebar class is responsible for toggling the sidebar
  .show-sidebar {
    transform: translate(0);
    z-index: 999;
  }
```

> Full code&#x20;
>
> {% code title="src/components/Sidebar.js" %}
>
> ```javascript
> import React from 'react'
> import logo from '../assets/logo.svg'
> import { Link } from 'react-router-dom'
> import { useProductsContext } from '../context/products_context'
> import { FaTimes } from 'react-icons/fa'
> import { links } from '../utils/constants'
> import styled from 'styled-components'
> import CartButtons from './CartButtons'
> import { useUserContext } from '../context/user_context'
>
> const Sidebar = () => {
>   // later we will use context value for isOpen, for now lets hardcode this
>   const isOpen = true
>   return (
>     <SidebarContainer>
>       {/* two ways to write the className conditionally (commenting out one) */}
>       {/* <aside className={`${isOpen ? 'sidebar show-sidebar' : 'sidebar'}`}> */}
>       <aside className={`sidebar ${isOpen && 'show-sidebar'}`}>
>         <div className="sidebar-header">
>           <img src={logo} alt="comfy sloth" className="logo" />
>           <button type="button" className="close-btn">
>             <FaTimes />
>           </button>
>         </div>
>         <ul className="links">
>           {links.map(({ id, text, url }) => {
>             return (
>               <li key={id}>
>                 <Link to={url}>{text}</Link>
>               </li>
>             )
>           })}
>           <li>
>             <Link to="/checkout">Checkout</Link>
>           </li>
>         </ul>
>         <CartButtons />
>       </aside>
>     </SidebarContainer>
>   )
> }
>
> const SidebarContainer = styled.div`
>   text-align: center;
>   .sidebar-header {
>     display: flex;
>     justify-content: space-between;
>     align-items: center;
>     padding: 1rem 1.5rem;
>   }
>   .close-btn {
>     font-size: 2rem;
>     background: transparent;
>     border-color: transparent;
>     color: var(--clr-primary-5);
>     transition: var(--transition);
>     cursor: pointer;
>     color: var(--clr-red-dark);
>     margin-top: 0.2rem;
>   }
>   .close-btn:hover {
>     color: var(--clr-red-light);
>   }
>   .logo {
>     justify-self: center;
>     height: 45px;
>   }
>   .links {
>     margin-bottom: 2rem;
>   }
>   .links a {
>     display: block;
>     text-align: left;
>     font-size: 1rem;
>     text-transform: capitalize;
>     padding: 1rem 1.5rem;
>     color: var(--clr-grey-3);
>     transition: var(--transition);
>     letter-spacing: var(--spacing);
>   }
>
>   .links a:hover {
>     padding: 1rem 1.5rem;
>     padding-left: 2rem;
>     background: var(--clr-grey-10);
>     color: var(--clr-grey-2);
>   }
>
>   .sidebar {
>     position: fixed;
>     top: 0;
>     left: 0;
>     width: 100%;
>     height: 100%;
>     background: var(--clr-white);
>     transition: var(--transition);
>     transform: translate(-100%);
>     z-index: -1;
>   }
>   .show-sidebar {
>     transform: translate(0);
>     z-index: 999;
>   }
>   .cart-btn-wrapper {
>     margin: 2rem auto;
>   }
>   @media screen and (min-width: 992px) {
>     .sidebar {
>       display: none;
>     }
>   }
> `
>
> export default Sidebar
> ```
>
> {% endcode %}

### 6. Sidebar toggle functionality implemented in Product context&#x20;

{% hint style="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*
{% endhint %}

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,&#x20;
  * 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
>
> ```javascript
> import React from 'react'
> import ReactDOM from 'react-dom/client'
> import './index.css'
> import App from './App'
>
> import { ProductsProvider } from './context/products_context'
> import { FilterProvider } from './context/filter_context'
> import { CartProvider } from './context/cart_context'
> import { UserProvider } from './context/user_context'
> import { Auth0Provider } from '@auth0/auth0-react'
>
> const root = ReactDOM.createRoot(document.getElementById('root'))
>
> root.render(
>   <ProductsProvider>
>     <App />
>   </ProductsProvider>
> )
>
> ```

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&#x20;
>
> {% code title="src/components/Navbar.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import logo from '../assets/logo.svg'
> import { FaBars } from 'react-icons/fa'
> import { Link } from 'react-router-dom'
> import { links } from '../utils/constants'
> import CartButtons from './CartButtons'
> import { useProductsContext } from '../context/products_context'
> import { useUserContext } from '../context/user_context'
>
> const Nav = () => {
>   const { openSidebar } = useProductsContext()
>   return (
>     <NavContainer>
>       <div className="nav-center">
>         <div className="nav-header">
>           <Link to="/">
>             <img src={logo} alt="comfy sloth" />
>           </Link>
>           <button type="button" onClick={openSidebar} className="nav-toggle">
>             <FaBars />
>           </button>
>         </div>
>         {/* nav-links will also be re-used in Sidebar */}
>         <ul className="nav-links">
>           {links.map((link) => {
>             const { id, text, url } = link
>             return (
>               <li key={id}>
>                 <Link to={url}>{text}</Link>
>               </li>
>             )
>           })}
>         </ul>
>         <CartButtons />
>       </div>
>     </NavContainer>
>   )
> }
>
> const NavContainer = styled.nav`
>   height: 5rem;
>   display: flex;
>   align-items: center;
>   justify-content: center;
>
>   .nav-center {
>     width: 90vw;
>     margin: 0 auto;
>     max-width: var(--max-width);
>   }
>   .nav-header {
>     display: flex;
>     align-items: center;
>     justify-content: space-between;
>     img {
>       width: 175px;
>       margin-left: -15px;
>     }
>   }
>   .nav-toggle {
>     background: transparent;
>     border: transparent;
>     color: var(--clr-primary-5);
>     cursor: pointer;
>     svg {
>       font-size: 2rem;
>     }
>   }
>   .nav-links {
>     display: none;
>   }
>   .cart-btn-wrapper {
>     display: none;
>   }
>   // nav-toggle will be hidden on small screen and will be display only if we have a minimum of 992px wide screen
>   @media (min-width: 992px) {
>     .nav-toggle {
>       display: none;
>     }
>     .nav-center {
>       display: grid;
>       grid-template-columns: auto 1fr auto;
>       align-items: center;
>     }
>     .nav-links {
>       display: flex;
>       justify-content: center;
>       li {
>         margin: 0 0.5rem;
>       }
>       a {
>         color: var(--clr-grey-3);
>         font-size: 1rem;
>         text-transform: capitalize;
>         letter-spacing: var(--spacing);
>         padding: 0.5rem;
>         &:hover {
>           border-bottom: 2px solid var(--clr-primary-7);
>         }
>       }
>     }
>     .cart-btn-wrapper {
>       display: grid;
>     }
>   }
> `
>
> export default Nav
>
> ```
>
> {% endcode %}
>
> {% code title="src/components/Sidebar.js" %}
>
> ```javascript
> import React from 'react'
> import logo from '../assets/logo.svg'
> import { Link } from 'react-router-dom'
> import { useProductsContext } from '../context/products_context'
> import { FaTimes } from 'react-icons/fa'
> import { links } from '../utils/constants'
> import styled from 'styled-components'
> import CartButtons from './CartButtons'
> import { useUserContext } from '../context/user_context'
>
> const Sidebar = () => {
>   // later we will use context value for isOpen, for now lets hardcode this
>   // const isOpen = true
>   const { closeSidebar, isSidebarOpen } = useProductsContext()
>   return (
>     <SidebarContainer>
>       {/* two ways to write the className conditionally (commenting out one) */}
>       {/* <aside className={`${isOpen ? 'sidebar show-sidebar' : 'sidebar'}`}> */}
>       <aside className={`sidebar ${isSidebarOpen && 'show-sidebar'}`}>
>         <div className="sidebar-header">
>           <img src={logo} alt="comfy sloth" className="logo" />
>           <button type="button" onClick={closeSidebar} className="close-btn">
>             <FaTimes />
>           </button>
>         </div>
>         <ul className="links">
>           {links.map(({ id, text, url }) => {
>             return (
>               <li key={id}>
>                 {/* we need to close the sidebar when any of the links is clicked */}
>                 <Link to={url} onClick={closeSidebar}>
>                   {text}
>                 </Link>
>               </li>
>             )
>           })}
>           <li>
>             <Link to="/checkout" onClick={closeSidebar}>
>               Checkout
>             </Link>
>           </li>
>         </ul>
>         <CartButtons />
>       </aside>
>     </SidebarContainer>
>   )
> }
>
> const SidebarContainer = styled.div`
>   text-align: center;
>   .sidebar-header {
>     display: flex;
>     justify-content: space-between;
>     align-items: center;
>     padding: 1rem 1.5rem;
>   }
>   .close-btn {
>     font-size: 2rem;
>     background: transparent;
>     border-color: transparent;
>     color: var(--clr-primary-5);
>     transition: var(--transition);
>     cursor: pointer;
>     color: var(--clr-red-dark);
>     margin-top: 0.2rem;
>   }
>   .close-btn:hover {
>     color: var(--clr-red-light);
>   }
>   .logo {
>     justify-self: center;
>     height: 45px;
>   }
>   .links {
>     margin-bottom: 2rem;
>   }
>   .links a {
>     display: block;
>     text-align: left;
>     font-size: 1rem;
>     text-transform: capitalize;
>     padding: 1rem 1.5rem;
>     color: var(--clr-grey-3);
>     transition: var(--transition);
>     letter-spacing: var(--spacing);
>   }
>
>   .links a:hover {
>     padding: 1rem 1.5rem;
>     padding-left: 2rem;
>     background: var(--clr-grey-10);
>     color: var(--clr-grey-2);
>   }
>
>   .sidebar {
>     position: fixed;
>     top: 0;
>     left: 0;
>     width: 100%;
>     height: 100%;
>     background: var(--clr-white);
>     transition: var(--transition);
>     transform: translate(-100%);
>     z-index: -1;
>   }
>   .show-sidebar {
>     transform: translate(0);
>     z-index: 999;
>   }
>   .cart-btn-wrapper {
>     margin: 2rem auto;
>   }
>   @media screen and (min-width: 992px) {
>     .sidebar {
>       display: none;
>     }
>   }
> `
>
> export default Sidebar
>
> ```
>
> {% endcode %}
>
> {% code title="src/components/CartButtons.js" %}
>
> ```javascript
> import React from 'react'
> import { FaShoppingCart, FaUserMinus, FaUserPlus } from 'react-icons/fa'
> import { Link } from 'react-router-dom'
> import styled from 'styled-components'
> import { useProductsContext } from '../context/products_context'
> import { useCartContext } from '../context/cart_context'
> import { useUserContext } from '../context/user_context'
>
> const CartButtons = () => {
>   const { closeSidebar } = useProductsContext()
>   return (
>     <Wrapper className="cart-btn-wrapper">
>       <Link to="/cart" className="cart-btn" onClick={closeSidebar}>
>         Cart
>         <span className="cart-container">
>           <FaShoppingCart />
>           <span className="cart-value">12</span>
>         </span>
>       </Link>
>       <button type="button" className="auth-btn" onClick={closeSidebar}>
>         Login
>         <FaUserPlus />
>       </button>
>     </Wrapper>
>   )
> }
>
> const Wrapper = styled.div`
>   display: grid;
>   grid-template-columns: 1fr 1fr;
>   align-items: center;
>   width: 225px;
>
>   .cart-btn {
>     color: var(--clr-grey-1);
>     font-size: 1.5rem;
>     letter-spacing: var(--spacing);
>     color: var(--clr-grey-1);
>     display: flex;
>
>     align-items: center;
>   }
>   .cart-container {
>     display: flex;
>     align-items: center;
>     position: relative;
>     svg {
>       height: 1.6rem;
>       margin-left: 5px;
>     }
>   }
>   .cart-value {
>     position: absolute;
>     top: -10px;
>     right: -16px;
>     background: var(--clr-primary-5);
>     width: 16px;
>     height: 16px;
>     display: flex;
>     align-items: center;
>     justify-content: center;
>     border-radius: 50%;
>     font-size: 0.75rem;
>     color: var(--clr-white);
>     padding: 12px;
>   }
>   .auth-btn {
>     display: flex;
>     align-items: center;
>     background: transparent;
>     border-color: transparent;
>     font-size: 1.5rem;
>     cursor: pointer;
>     color: var(--clr-grey-1);
>     letter-spacing: var(--spacing);
>     svg {
>       margin-left: 5px;
>     }
>   }
> `
> export default CartButtons
>
> ```
>
> {% endcode %}
>
> {% code title="src/context/products\_context.js" %}
>
> ```javascript
> import axios from 'axios'
> import React, { useContext, useEffect, useReducer } from 'react'
> import reducer from '../reducers/products_reducer'
> import { products_url as url } from '../utils/constants'
> import {
>   SIDEBAR_OPEN,
>   SIDEBAR_CLOSE,
>   GET_PRODUCTS_BEGIN,
>   GET_PRODUCTS_SUCCESS,
>   GET_PRODUCTS_ERROR,
>   GET_SINGLE_PRODUCT_BEGIN,
>   GET_SINGLE_PRODUCT_SUCCESS,
>   GET_SINGLE_PRODUCT_ERROR,
> } from '../actions'
>
> const initialState = {
>   isSidebarOpen: false,
> }
>
> const ProductsContext = React.createContext()
>
> export const ProductsProvider = ({ children }) => {
>   const [state, dispatch] = useReducer(reducer, initialState)
>
>   const openSidebar = () => {
>     dispatch({ type: SIDEBAR_OPEN })
>   }
>
>   const closeSidebar = () => {
>     dispatch({ type: SIDEBAR_CLOSE })
>   }
>
>   return (
>     <ProductsContext.Provider value={{ ...state, openSidebar, closeSidebar }}>
>       {children}
>     </ProductsContext.Provider>
>   )
> }
> // make sure use
> export const useProductsContext = () => {
>   return useContext(ProductsContext)
> }
>
> ```
>
> {% endcode %}
>
> {% code title="src/reducer/products\_reducer.js" %}
>
> ```javascript
> import {
>   SIDEBAR_OPEN,
>   SIDEBAR_CLOSE,
>   GET_PRODUCTS_BEGIN,
>   GET_PRODUCTS_SUCCESS,
>   GET_PRODUCTS_ERROR,
>   GET_SINGLE_PRODUCT_BEGIN,
>   GET_SINGLE_PRODUCT_SUCCESS,
>   GET_SINGLE_PRODUCT_ERROR,
> } from '../actions'
>
> const products_reducer = (state, action) => {
>   if (action.type === SIDEBAR_OPEN) {
>     return { ...state, isSidebarOpen: true }
>   }
>   if (action.type === SIDEBAR_CLOSE) {
>     return { ...state, isSidebarOpen: false }
>   }
>   throw new Error(`No Matching "${action.type}" - action type`)
> }
>
> export default products_reducer
>
> ```
>
> {% endcode %}

{% hint style="success" %}
At this point you should have the below functionality to open and close the sidebar
{% endhint %}

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FIm5C2qPhZzeoShv4sAdd%2Fimage.png?alt=media&#x26;token=dd7db89e-ae9c-4bc7-b9fc-2ef9c331143b" alt=""><figcaption><p>Click this to open sidebar</p></figcaption></figure>

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2Fxmg7fCcioa408w06ECGF%2Fimage.png?alt=media&#x26;token=41cfef4d-d176-4f9b-8e06-2280c9b4e3eb" alt=""><figcaption><p>close sidebar by clicking any of these</p></figcaption></figure>

{% hint style="warning" %}

* From now on, while pasting full code, I won't include the styled component's Wrapper code.&#x20;
* Also, in the file caption, I omit src/ in file names
  {% endhint %}

### 7. Error page

{% hint style="info" %}
25\_ecommerce\_app/src/pages/ErrorPage.js
{% endhint %}

Our intention is to design the error page like this

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FVjq9i0YNaV7tEGXbVenB%2Fimage.png?alt=media&#x26;token=21efe128-3bc5-4278-9db5-644d4e70a574" alt=""><figcaption></figcaption></figure>

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

```css
.page-100 {
  min-height: calc(100vh - 10rem);
  padding: 5rem 0;
}
```

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

```css
.btn {
  text-transform: uppercase;
  background: var(--clr-primary-5);
  color: var(--clr-primary-10);
  padding: 0.375rem 0.75rem;
  letter-spacing: var(--spacing);
  display: inline-block;
  font-weight: 400;
  transition: var(--transition);
  font-size: 0.875rem;
  cursor: pointer;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
  border-radius: var(--radius);
  border-color: transparent;
}
```

> Full code&#x20;
>
> {% code title="pages/ErrorPage.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { Link } from 'react-router-dom'
> const ErrorPage = () => {
>   return (
>     <Wrapper className="page-100">
>       <section>
>         <h1>404</h1>
>         <h3>Sorry, the page you tried cannot be found</h3>
>         <Link to="/" className="btn">
>           Back home
>         </Link>
>       </section>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}

### 8. About and Checkout Page

{% hint style="info" %}
25\_ecommerce\_app/src/pages/AboutPage.js

25\_ecommerce\_app/src/pages/CheckoutPage.js

25\_ecommerce\_app/src/components/PageHero.js
{% endhint %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FHB81vfCDp2aWJKRJY6AT%2Fimage.png?alt=media&#x26;token=87272531-7004-4e17-80a8-6e97c4420d0f" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FYc6eB67rLvejHjyUT6xd%2Fimage.png?alt=media&#x26;token=465dc824-7307-472e-9070-d4e00c01abe1" alt=""><figcaption></figcaption></figure>

> Full code&#x20;
>
> {% code title="components/PageHero.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { Link } from 'react-router-dom'
> const PageHero = ({ title }) => {
>   return (
>     <Wrapper>
>       <div className="section-center">
>         <h3>
>           <Link to="/">Home </Link> / {title}
>         </h3>
>       </div>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}
>
> {% code title="pages/AboutPage.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { PageHero } from '../components'
> import aboutImg from '../assets/hero-bcg.jpeg'
>
> const AboutPage = () => {
>   return (
>     <main>
>       <PageHero title="about" />
>       {/* The classes here is as follows defined in index.css
>     - page - min-height: calc(100vh - (20vh + 10rem)); ->  to give a fixed height
>     - section - padding: 5rem 0; -> to give little top and bottom padding
>     - section-center -> to center the content on the page
>     */}
>
>       {/* wrapper is a two column layout here defined using the Grid in Wrapper */}
>       <Wrapper className="page section section-center">
>         <img src={aboutImg} alt="Nice desk" />
>         <article>
>           <div className="title">
>             <h2>our story</h2>
>             <div className="underline"></div>
>           </div>
>           <p>
>             Lorem ipsum dolor sit amet, consectetur adipisicing elit. Adipisci,
>             veniam non cumque velit doloribus expedita culpa mollitia maiores
>             unde harum sed incidunt laboriosam animi explicabo repellat commodi
>             iste quasi quidem.
>           </p>
>         </article>
>       </Wrapper>
>     </main>
>   )
> }
> ```
>
> {% endcode %}
>
> {% code title="pages/Checkout.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { PageHero, StripeCheckout } from '../components'
> // extra imports
> import { useCartContext } from '../context/cart_context'
> import { Link } from 'react-router-dom'
>
> const CheckoutPage = () => {
>   return (
>     <main>
>       <PageHero title="checkout" />
>       <Wrapper className="page">
>         <h3>Checkout here (Will work on this later)</h3>
>       </Wrapper>
>     </main>
>   )
> }
> const Wrapper = styled.div``
> export default CheckoutPage
> ```
>
> {% endcode %}

### 9. Home page

{% hint style="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
{% endhint %}

Let's now design Home page.&#x20;

*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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FyX4xiqsnYOzdB2uhqEmr%2Fimage.png?alt=media&#x26;token=fa17a07a-0182-41e0-b227-7e7cc33ffebb" alt=""><figcaption></figcaption></figure>

```javascript
import React from 'react'
import styled from 'styled-components'
import { services } from '../utils/constants'

const Services = () => {
  return <h4>services </h4>
}
```

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/constant`s. The `{services}` file looks like this

```javascript
import React from 'react'
import { GiCompass, GiDiamondHard, GiStabbedNote } from 'react-icons/gi'
export const links = [
  {
    id: 1,
    text: 'home',
    url: '/',
  },
  {
    id: 2,
    text: 'about',
    url: '/about',
  },
  {
    id: 3,
    text: 'products',
    url: '/products',
  },
]

export const services = [
  {
    id: 1,
    icon: <GiCompass />,
    title: 'mission',
    text:
      'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Voluptates, ea. Perferendis corrupti reiciendis nesciunt rerum velit autem unde numquam nisi',
  },
  {
    id: 2,
    icon: <GiDiamondHard />,
    title: 'vision',
    text:
      'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Voluptates, ea. Perferendis corrupti reiciendis nesciunt rerum velit autem unde numquam nisi',
  },
  {
    id: 3,
    icon: <GiStabbedNote />,
    title: 'history',
    text:
      'Lorem ipsum, dolor sit amet consectetur adipisicing elit. Voluptates, ea. Perferendis corrupti reiciendis nesciunt rerum velit autem unde numquam nisi',
  },
]

export const products_url = 'https://course-api.com/react-store-products'

export const single_product_url = `https://course-api.com/react-store-single-product?id=`

```

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
>
> {% code title="components/Hero.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { Link } from 'react-router-dom'
> import heroBcg from '../assets/hero-bcg.jpeg'
> import heroBcg2 from '../assets/hero-bcg-2.jpeg'
>
> const Hero = () => {
>   return (
>     <Wrapper className="section-center">
>       <article className="content">
>         <h1>
>           design your <br /> comfort zone
>         </h1>
>         <p>
>           Lorem ipsum dolor sit amet consectetur adipisicing elit. Quod
>           molestias illo iste pariatur dignissimos, ab beatae magnam modi
>           maiores doloremque quidem nostrum dolorem!
>         </p>
>         <Link to="/products" className="btn hero-btn">
>           shop now
>         </Link>
>       </article>
>       <article className="img-container">
>         <img src={heroBcg} alt="nice table" className="main-img" />
>         <img src={heroBcg2} alt="person working" className="accent-img" />
>       </article>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}
>
> {% code title="components/Services.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { services } from '../utils/constants'
>
> const Services = () => {
>   return (
>     <Wrapper>
>       <div className="section-center">
>         <article className="header">
>           <h3>
>             custom furniture <br /> built only for you{' '}
>           </h3>
>           <p>
>             Lorem ipsum dolor sit, amet consectetur adipisicing elit. Nihil
>             fugit fugiat ullam eligendi blanditiis. Ad veritatis suscipit, est
>             nihil nisi eum! Maiores hic soluta.
>           </p>
>         </article>
>         <div className="services-center">
>           {services.map((service) => {
>             const { id, icon, title, text } = service
>             return (
>               <article key={id} className="service">
>                 <span className="icon">{icon}</span>
>                 <h4>{title}</h4>
>                 <p>{text}</p>
>               </article>
>             )
>           })}
>         </div>
>       </div>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}
>
> {% code title="components/Contact.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
>
> const Contact = () => {
>   return (
>     <Wrapper>
>       <div className="section-center">
>         <h3>Join our news letter and get 20% off</h3>
>         <div className="content">
>           <p>
>             Lorem ipsum dolor sit amet consectetur adipisicing elit. Optio quis
>             consequuntur ex in at alias labore quibusdam tenetur ab quia
>             voluptatum impedit, minus temporibus dicta quidem corrupti vitae
>             itaque, earum nihil qui! Quidem repellat eos ex ab dolorum facilis
>             officiis doloremque unde nostrum magni, voluptas dolores iure
>             perspiciatis. Magni, vitae.
>           </p>
>           <form className="contact-form">
>             <input
>               type="text"
>               className="form-input"
>               placeholder="enter email"
>             />
>             <button className="submit-btn">subscribe</button>
>           </form>
>         </div>
>       </div>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}
>
> {% code title="pages/HomePage.js" %}
>
> ```javascript
> import React from 'react'
> import { FeaturedProducts, Hero, Services, Contact } from '../components'
> const HomePage = () => {
>   return (
>     <main>
>       <Hero />
>       <FeaturedProducts />
>       <Services />
>       <Contact />
>     </main>
>   )
> }
>
> export default HomePage
>
> ```
>
> {% endcode %}

### 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.&#x20;

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`

```javascript
export const products_url = 'https://course-api.com/react-store-products'

export const single_product_url = `https://course-api.com/react-store-single-product?id=`
```

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.

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FqKoQtRn3OTaMG7bQhnvF%2Fimage.png?alt=media&#x26;token=e965bf8b-2ced-4808-83fd-f257484671eb" alt=""><figcaption></figcaption></figure>

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

### 11. Fetch Products (And also set featured products)

{% hint style="info" %}
25\_ecommerce\_app/src/context/products\_context.js

25\_ecommerce\_app/src/reducers/products\_reducer.js
{% endhint %}

{% code title="context/products\_context.js" %}

```javascript
  // fetch products
  const fetchProducts = async (url) => {
    const response = await axios.get(url)
    console.log(response.data)
  }

  // fetch the products once and show the featured ones to HomePage. Show all products in /products page
  useEffect(() => {
    fetchProducts(url)
  }, [])
```

{% endcode %}

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FeudheR1Jl8H1VePmNkoY%2Fimage.png?alt=media&#x26;token=dba939a6-9ddb-4af0-b8c2-02b7300ad12c" alt=""><figcaption></figcaption></figure>

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
>
> {% code title="context/products\_context.js" %}
>
> ```javascript
> import axios from 'axios'
> import React, { useContext, useEffect, useReducer } from 'react'
> import reducer from '../reducers/products_reducer'
> import { products_url as url } from '../utils/constants'
> import {
>   SIDEBAR_OPEN,
>   SIDEBAR_CLOSE,
>   GET_PRODUCTS_BEGIN,
>   GET_PRODUCTS_SUCCESS,
>   GET_PRODUCTS_ERROR,
>   GET_SINGLE_PRODUCT_BEGIN,
>   GET_SINGLE_PRODUCT_SUCCESS,
>   GET_SINGLE_PRODUCT_ERROR,
> } from '../actions'
>
> const initialState = {
>   isSidebarOpen: false,
>   products_loading: false, // loading for all products. We will soon add loading for single product
>   products_error: false,
>   products: [], // this is shown on the products page
>   featured_products: [], // this is shown on the home page
>   // 3 more props to come next. loading, error and product props - for singleProduct
> }
>
> const ProductsContext = React.createContext()
>
> export const ProductsProvider = ({ children }) => {
>   const [state, dispatch] = useReducer(reducer, initialState)
>
>   const openSidebar = () => {
>     dispatch({ type: SIDEBAR_OPEN })
>   }
>
>   const closeSidebar = () => {
>     dispatch({ type: SIDEBAR_CLOSE })
>   }
>
>   // fetch products
>   const fetchProducts = async (url) => {
>     dispatch({ type: GET_PRODUCTS_BEGIN }) // this is to set the loading to true
>     try {
>       const response = await axios.get(url)
>       const products = response.data
>       dispatch({ type: GET_PRODUCTS_SUCCESS, payload: products }) // this is to set products array and also featured products and then loading to false
>     } catch (error) {
>       dispatch({ type: GET_PRODUCTS_ERROR }) // this sets loading to false and error to true for products
>     }
>   }
>
>   // fetch the products once and show the featured ones to HomePage. Show all products in /products page
>   useEffect(() => {
>     fetchProducts(url)
>   }, [])
>
>   return (
>     <ProductsContext.Provider value={{ ...state, openSidebar, closeSidebar }}>
>       {children}
>     </ProductsContext.Provider>
>   )
> }
> // make sure use
> export const useProductsContext = () => {
>   return useContext(ProductsContext)
> }
> ```
>
> {% endcode %}
>
> {% code title="reducers/products\_reducer.js" %}
>
> ```javascript
> import {
>   SIDEBAR_OPEN,
>   SIDEBAR_CLOSE,
>   GET_PRODUCTS_BEGIN,
>   GET_PRODUCTS_SUCCESS,
>   GET_PRODUCTS_ERROR,
>   GET_SINGLE_PRODUCT_BEGIN,
>   GET_SINGLE_PRODUCT_SUCCESS,
>   GET_SINGLE_PRODUCT_ERROR,
> } from '../actions'
>
> const products_reducer = (state, action) => {
>   if (action.type === SIDEBAR_OPEN) {
>     return { ...state, isSidebarOpen: true }
>   }
>   if (action.type === SIDEBAR_CLOSE) {
>     return { ...state, isSidebarOpen: false }
>   }
>   if (action.type === GET_PRODUCTS_BEGIN) {
>     return { ...state, products_loading: true }
>   }
>   if (action.type === GET_PRODUCTS_SUCCESS) {
>     const products = action.payload
>     const featured_products = products.filter((product) => product.featured)
>     return {
>       ...state,
>       products_loading: false,
>       products,
>       featured_products,
>     }
>   }
>   if (action.type === GET_PRODUCTS_ERROR) {
>     return { ...state, products_loading: false, products_error: true }
>   }
>   throw new Error(`No Matching "${action.type}" - action type`)
> }
>
> export default products_reducer
> ```
>
> {% endcode %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2Fq7K1Pc7lFy5qA0LfkyGt%2Fimage.png?alt=media&#x26;token=e58e3899-5d59-48de-9fab-057203a62960" alt=""><figcaption></figcaption></figure>

### 12. Featured Products on HomePage, Error and Loading

{% hint style="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
{% endhint %}

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 [#9.-home-page](#9.-home-page "mention")

> Full code
>
> {% code title="components/Loading.js" %}
>
> ```javascript
> import React from 'react'
>
> const Loading = () => {
>   return (
>     <div className="section section-center">
>       <div className="loading"></div>
>     </div>
>   )
> }
>
> export default Loading
> ```
>
> {% endcode %}
>
> {% code title="components/Error.js" %}
>
> ```javascript
> import React from 'react'
> const Error = () => {
>   return (
>     <div className="section section-center text-center">
>       <h2>there was an error....</h2>
>     </div>
>   )
> }
>
> export default Error
> ```
>
> {% endcode %}
>
> {% code title="components/Product.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { formatPrice } from '../utils/helpers'
> import { FaSearch } from 'react-icons/fa'
> import { Link } from 'react-router-dom'
>
> const Product = ({ image, name, price, id }) => {
>   return (
>     <Wrapper>
>       <div className="container">
>         <img src={image} alt={name} />
>         <Link to={`/products/${id}`} className="link">
>           <FaSearch />
>         </Link>
>       </div>
>       <footer>
>         <h5>{name}</h5>
>         <p>{price}</p>
>       </footer>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}
>
> {% code title="components/FeaturedProducts.js" %}
>
> ```javascript
> import React from 'react'
> import { useProductsContext } from '../context/products_context'
> import { Link } from 'react-router-dom'
> import styled from 'styled-components'
> import Error from './Error'
> import Loading from './Loading'
> import Product from './Product'
>
> const FeaturedProducts = () => {
>   const {
>     featured_products,
>     products_loading: loading,
>     products_error: error,
>   } = useProductsContext()
>
>   if (loading) {
>     return <Loading />
>   }
>   if (error) {
>     return <Error />
>   }
>
>   return (
>     <Wrapper className="section">
>       <div className="title">
>         <h2>featured products</h2>
>         <div className="underline"></div>
>       </div>
>       <div className="section-center featured">
>         {/* we will display only 3 products as featured products */}
>         {featured_products.slice(0, 3).map((product) => {
>           return <Product key={product.id} {...product} />
>         })}
>       </div>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FwwGaDhNLfdV0SSmmLi5x%2Fimage.png?alt=media&#x26;token=7f4c3a97-3651-4cd2-b01c-3e96f8a8c12c" alt=""><figcaption></figcaption></figure>

### 13.  Format Price

{% hint style="info" %}
25\_ecommerce\_app/src/utils/helpers.js

25\_ecommerce\_app/src/components/Product.js
{% endhint %}

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.&#x20;

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.&#x20;

So let's do that util function now.

{% code title="components/Product.js" %}

```javascript
  <footer>
    <h5>{name}</h5>
    <p>{formatPrice(price)}</p> // formatPrice is the util function
  </footer>
```

{% endcode %}

> Full code
>
> {% code title="utils/helper.js" %}
>
> ```javascript
> export const formatPrice = (number) => {
>   const formattedNumber = new Intl.NumberFormat('en-US', {
>     style: 'currency',
>     currency: 'USD',
>   }).format(number / 100) // note that we still need to divide by 100 but don't have to format the decimals
>   return formattedNumber // it automatically adds the currency representation (Adds $ at the beginning)
> }
>
> export const getUniqueValues = () => {}
>
> ```
>
> {% endcode %}
>
> {% code title="components/Product.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { formatPrice } from '../utils/helpers'
> import { FaSearch } from 'react-icons/fa'
> import { Link } from 'react-router-dom'
>
> const Product = ({ image, name, price, id }) => {
>   return (
>     <Wrapper>
>       <div className="container">
>         <img src={image} alt={name} />
>         <Link to={`/products/${id}`} className="link">
>           <FaSearch />
>         </Link>
>       </div>
>       <footer>
>         <h5>{name}</h5>
>       
>         {/* formatPrice() function gives back the currency symbol as well so we don't have to place it here */}
>       
>         <p>{formatPrice(price)}</p>
>       </footer>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}

### 14. Single Product Functionality Part 1

{% hint style="info" %}
25\_ecommerce\_app/src/context/products\_context.js

25\_ecommerce\_app/src/reducers/products\_reducer.js
{% endhint %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FZbmtD5oZ0ejQNN40ROCR%2Fimage.png?alt=media&#x26;token=7a794808-c1a0-4be5-8e2a-8dd5e0c13a6e" alt=""><figcaption></figcaption></figure>

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

```javascript
 // fetch single product
  const fetchSingleProduct = async (url) => {
    dispatch({ type: GET_SINGLE_PRODUCT_BEGIN }) // this is to set the loading to true for single product
    try {
      const response = await axios.get(url)
      const singleProduct = response.data
      dispatch({ type: GET_SINGLE_PRODUCT_SUCCESS, payload: singleProduct })
    } catch (error) {
      dispatch({ type: GET_SINGLE_PRODUCT_ERROR })
    }
  }
```

> Full code&#x20;
>
> {% code title="context/products\_context.js" %}
>
> ```javascript
> import axios from 'axios'
> import React, { useContext, useEffect, useReducer } from 'react'
> import reducer from '../reducers/products_reducer'
> import { products_url as url } from '../utils/constants'
> import {
>   SIDEBAR_OPEN,
>   SIDEBAR_CLOSE,
>   GET_PRODUCTS_BEGIN,
>   GET_PRODUCTS_SUCCESS,
>   GET_PRODUCTS_ERROR,
>   GET_SINGLE_PRODUCT_BEGIN,
>   GET_SINGLE_PRODUCT_SUCCESS,
>   GET_SINGLE_PRODUCT_ERROR,
> } from '../actions'
>
> const initialState = {
>   isSidebarOpen: false,
>   products_loading: false, // loading for all products.
>   products_error: false,
>   products: [], // this is shown on the products page
>   featured_products: [], // this is shown on the home page
>   // for singleProduct
>   single_product_loading: false,
>   single_product_error: false,
>   single_product: {},
> }
>
> const ProductsContext = React.createContext()
>
> export const ProductsProvider = ({ children }) => {
>   const [state, dispatch] = useReducer(reducer, initialState)
>
>   const openSidebar = () => {
>     dispatch({ type: SIDEBAR_OPEN })
>   }
>
>   const closeSidebar = () => {
>     dispatch({ type: SIDEBAR_CLOSE })
>   }
>
>   // fetch products
>   const fetchProducts = async (url) => {
>     dispatch({ type: GET_PRODUCTS_BEGIN }) // this is to set the loading to true
>     try {
>       const response = await axios.get(url)
>       const products = response.data
>       dispatch({ type: GET_PRODUCTS_SUCCESS, payload: products }) // this is to set products array and also featured products and then loading to false
>     } catch (error) {
>       dispatch({ type: GET_PRODUCTS_ERROR }) // this sets loading to false and error to true for products
>     }
>   }
>
>   // fetch the products once and show the featured ones to HomePage. Show all products in /products page
>   useEffect(() => {
>     fetchProducts(url)
>   }, [])
>
>   // fetch single product - using useCallback to avoid creating fetchSingleProduct everytime
>   // which is optional
>   const fetchSingleProduct = React.useCallback(async (url) => {
>     dispatch({ type: GET_SINGLE_PRODUCT_BEGIN }) // this is to set the loading to true for single product
>     try {
>       const response = await axios.get(url)
>       const singleProduct = response.data
>       dispatch({ type: GET_SINGLE_PRODUCT_SUCCESS, payload: singleProduct })
>     } catch (error) {
>       dispatch({ type: GET_SINGLE_PRODUCT_ERROR })
>     }
>   }, [])
>
>
>   return (
>     <ProductsContext.Provider
>       value={{ ...state, openSidebar, closeSidebar, fetchSingleProduct }}
>     >
>       {children}
>     </ProductsContext.Provider>
>   )
> }
> // make sure use
> export const useProductsContext = () => {
>   return useContext(ProductsContext)
> }
> ```
>
> {% endcode %}
>
> {% code title="reducers/products\_reducer.js" %}
>
> ```javascript
> import {
>   SIDEBAR_OPEN,
>   SIDEBAR_CLOSE,
>   GET_PRODUCTS_BEGIN,
>   GET_PRODUCTS_SUCCESS,
>   GET_PRODUCTS_ERROR,
>   GET_SINGLE_PRODUCT_BEGIN,
>   GET_SINGLE_PRODUCT_SUCCESS,
>   GET_SINGLE_PRODUCT_ERROR,
> } from '../actions'
>
> const products_reducer = (state, action) => {
>   if (action.type === SIDEBAR_OPEN) {
>     return { ...state, isSidebarOpen: true }
>   }
>   if (action.type === SIDEBAR_CLOSE) {
>     return { ...state, isSidebarOpen: false }
>   }
>
>   // products and featured products
>
>   if (action.type === GET_PRODUCTS_BEGIN) {
>     return { ...state, products_loading: true, products_error: false } // setting up products error to be false just in case if it was true last time
>   }
>   if (action.type === GET_PRODUCTS_SUCCESS) {
>     const products = action.payload
>     const featured_products = products.filter((product) => product.featured)
>     return {
>       ...state,
>       products_loading: false,
>       products_error: false,
>       products,
>       featured_products,
>     }
>   }
>   if (action.type === GET_PRODUCTS_ERROR) {
>     return { ...state, products_loading: false, products_error: true }
>   }
>
>   // single product
>
>   if (action.type === GET_SINGLE_PRODUCT_BEGIN) {
>     return {
>       ...state,
>       single_product_loading: true,
>       single_product_error: false, // setting up products error to be false just in case if it was true last time
>     }
>   }
>   if (action.type === GET_SINGLE_PRODUCT_SUCCESS) {
>     return {
>       ...state,
>       single_product_loading: false,
>       single_product_error: false,
>       single_product: action.payload,
>     }
>   }
>   if (action.type === GET_SINGLE_PRODUCT_ERROR) {
>     return {
>       ...state,
>       single_product_loading: false,
>       single_product_error: true,
>     }
>   }
>
>   throw new Error(`No Matching "${action.type}" - action type`)
> }
>
> export default products_reducer
>
> ```
>
> {% endcode %}

### 15. Single Product Functionality Part 2

{% hint style="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
{% endhint %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FcXRZtAnfZZ6crnmjyBY7%2Fimage.png?alt=media&#x26;token=6a45f9e7-e1eb-4904-834c-5ca8dfa90d62" alt=""><figcaption></figcaption></figure>

In App.js we have this code to display SingleProduct

```javascript
<Route exact path="/products/:id" children={<SingleProduct />} />
// this :id is what we can get from useParams hook inside singleProduct page
```

```javascript
const SingleProductPage = () => {
  const { fetchSingleProduct } = useProductsContext()
  const { id } = useParams()
  console.log('The id is', id) // this is that id defined in App.js for singleProduct
```

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

{% code lineNumbers="true" %}

```javascript
const SingleProductPage = () => {
  const {
    fetchSingleProduct,
    single_product_loading: loading,
    single_product_error: error,
    single_product: product,
  } = useProductsContext()

  const { id } = useParams()
  console.log('The id is', id)

  useEffect(() => {
    fetchSingleProduct(`${url}${id}`)
  }, [fetchSingleProduct, id])
  console.log(product)
  return <h4>single product page</h4>
}
```

{% endcode %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2F2huHIBqVPKRu3ZNgsn2P%2Fimage.png?alt=media&#x26;token=2e6ecde8-89c2-4f1a-bc9d-c10a98aaa570" alt=""><figcaption></figcaption></figure>

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

```javascript
const SingleProductPage = () => {
  const {
    fetchSingleProduct,
    single_product_loading: loading,
    single_product_error: error,
    single_product: product,
  } = useProductsContext()

  const { id } = useParams()

  useEffect(() => {
    fetchSingleProduct(`${url}${id}`)
  }, [fetchSingleProduct, id])

  if (loading) {
    return <Loading />
  }
  if (error) {
    return <Error />
  }
  return <h4>single product page</h4>
}
```

Now change the single\_product\_url and it should show you the error like this

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FM5rFvwbN1lbIVQ3KLKmO%2Fimage.png?alt=media&#x26;token=e43e3f01-794b-4265-aadd-29ce57db555b" alt=""><figcaption></figcaption></figure>

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

```javascript
  // we will navigate back to home screen after 3 sec if there 
  // is an error in fetching single product
  // To test this, change single product url and you should see this behaviour
  useEffect(() => {
    if (error) {
      setTimeout(() => {
        history.push('/')
      }, 3000)
    }
  }, [error, history])
```

Our code currently looks this way

```javascript
const SingleProductPage = () => {
  const {
    fetchSingleProduct,
    single_product_loading: loading,
    single_product_error: error,
    single_product: product,
  } = useProductsContext()

  const { id } = useParams()
  const history = useHistory()

  useEffect(() => {
    fetchSingleProduct(`${url}${id}`)
  }, [fetchSingleProduct, id])

  // we will navigate back to home screen after 3 sec if there is an error in fetching single product
  // To test this, change single product url and you should see this behaviour
  useEffect(() => {
    if (error) {
      setTimeout(() => {
        history.push('/')
      }, 3000)
    }
  }, [error, history])

  if (loading) {
    return <Loading />
  }
  if (error) {
    return <Error />
  }
  const {
    id: sku,
    stock,
    price,
    shipping,
    featured,
    colors,
    category,
    images,
    reviews,
    stars,
    name,
    description,
    company,
  } = product
  console.log(product)

  return (
    <Wrapper>
      <PageHero title={name} product />
      <div className="section section-center page">
        <Link to="/products" className="btn">
          Back to products
        </Link>
        <div className="product-center">
          <ProductImages />
          <section className="content">
            <h2>{name}</h2>
            <Stars />
            <h5 className="price">{formatPrice(price)}</h5>
            <p className="desc">{description}</p>
            <p className="info">
              <span>Available : </span>
              {stock > 0 ? 'In stock' : 'Out of stock'}
            </p>
            <p className="info">
              <span>SKU : </span>
              {sku}
            </p>
            <p className="info">
              <span>Brand : </span>
              {company}
            </p>
            <hr />
            {stock > 0 && <AddToCart />}
          </section>
        </div>
      </div>
    </Wrapper>
  )
}
```

And the `singleProduct` page (due to code above) looks like this&#x20;

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FWZB3BP9k33Cnk0hab7DK%2Fimage.png?alt=media&#x26;token=928c763e-d799-42cb-bfa7-378fe76b8157" alt=""><figcaption></figcaption></figure>

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)

{% code title="components/PageHero.js" %}

```javascript
import React from 'react'
import styled from 'styled-components'
import { Link } from 'react-router-dom'
// the page hero for single product will be a bit different.

/* For Products ----> Home / Products
   For Single Product -----> Home / Products / Chair
*/

// to achieve the above let's do a conditional logic using product prop. 
// If it exists then it is for single product
const PageHero = ({ title, product }) => {
  return (
    <Wrapper>
      <div className="section-center">
        <h3>
          <Link to="/">Home </Link>/{' '}
          {product && <Link to="/products">Products /</Link>}
          {title}
        </h3>
      </div>
    </Wrapper>
  )
}
```

{% endcode %}

#### Product Images

Let's now work on Product Images part.&#x20;

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)

```javascript
<ProductImages images={images} /> // inside SingleProductPage
```

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FT56tFkoiqaqyQukmtVPt%2Fimage.png?alt=media&#x26;token=7b2ce508-961b-4765-a393-64dca2ce82e6" alt=""><figcaption></figcaption></figure>

> Full Code for ProductImages
>
> {% code title="components/ProductImages.js" %}
>
> ```javascript
> import React, { useState } from 'react'
> import styled from 'styled-components'
>
> const ProductImages = ({ images = [{ url: '' }] }) => {
>   // initially images will be empty so setting a default here to [] and adding first prop with url = '' as we are using it below
>   const [mainImage, setMainImage] = useState(images[0])
>
>   return (
>     <Wrapper>
>       <img src={mainImage.url} alt="main" />
>       <div className="gallery">
>         {images.map((image, index) => {
>           return (
>             <img
>               onClick={() => setMainImage(images[index])}
>               key={index}
>               src={image.url}
>               alt={image.filename}
>               className={`${image.url === mainImage.url ? 'active' : null}`} // to set the active class on image to show which one is the main image currently
>             />
>           )
>         })}
>       </div>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}

#### Stars

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FtbFcYSgCXoqMnBVROGxO%2Fimage.png?alt=media&#x26;token=d726ef76-fd1c-4df9-b474-d8c2cd02a8d6" alt=""><figcaption></figcaption></figure>

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

**Stars - Manual Approach**&#x20;

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FWZpyqhXuldv0PzCQJmO6%2Fimage.png?alt=media&#x26;token=20eb77f6-348f-469f-a03a-dd8d45e0b133" alt=""><figcaption></figcaption></figure>

The above is a single star

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FQPwdgYgRfKi49VJHX3NK%2Fimage.png?alt=media&#x26;token=78f84063-f1a7-49af-82b4-73eeae730421" alt=""><figcaption></figcaption></figure>

Let's add five stars the same way&#x20;

```javascript
<Wrapper>
      <div className="stars">
        {/* first star start */}
        <span>
          {stars >= 1 ? (
            <BsStarFill />
          ) : stars >= 0.5 ? (
            <BsStarHalf />
          ) : (
            <BsStar />
          )}
        </span>
        {/*  first star end */}
        {/* second star start */}
        <span>
          {stars >= 2 ? (
            <BsStarFill />
          ) : stars >= 1.5 ? (
            <BsStarHalf />
          ) : (
            <BsStar />
          )}
        </span>
        {/*  second star end */}
        {/* third star start */}
        <span>
          {stars >= 3 ? (
            <BsStarFill />
          ) : stars >= 2.5 ? (
            <BsStarHalf />
          ) : (
            <BsStar />
          )}
        </span>
        {/*  third star end */}
        {/* fourth star start */}
        <span>
          {stars >= 4 ? (
            <BsStarFill />
          ) : stars >= 3.5 ? (
            <BsStarHalf />
          ) : (
            <BsStar />
          )}
        </span>
        {/*  fourth star end */}
        {/* fifth star start */}
        <span>
          {stars === 5 ? (
            <BsStarFill />
          ) : stars >= 4.5 ? (
            <BsStarHalf />
          ) : (
            <BsStar />
          )}
        </span>
        {/*  fifth star end */}
      </div>
      <div className="reviews">({reviews} reviews) </div>
    </Wrapper>
```

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FJL7Rj9xcxhy7UWuz5uDL%2Fimage.png?alt=media&#x26;token=ec241777-bcc2-4707-89a3-030faa5fbcaf" alt=""><figcaption></figcaption></figure>

**Stars - Programmatic Approach**&#x20;

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/10824697>

```javascript
{Array.from({ length: 5 }, (_, index) => {
  return (
    <span key={index}>
      {stars >= index + 1 ? (
        <BsStarFill />
      ) : stars >= index + 0.5 ? (
        <BsStarHalf />
      ) : (
        <BsStar />
      )}
    </span>
  )
})}
```

> Full code for Stars
>
> {% code title="components/Stars.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { BsStarFill, BsStarHalf, BsStar } from 'react-icons/bs'
> const Stars = ({ stars, reviews }) => {
>   return (
>     <Wrapper>
>       <div className="stars">
>         {/* <span>
>           {stars >= 1 ? (
>             <BsStarFill />
>           ) : stars >= 0.5 ? (
>             <BsStarHalf />
>           ) : (
>             <BsStar />
>           )}
>         </span> */}
>
>         {/* MY STACKOVERFLOW REFERENCE -  https://stackoverflow.com/a/68029192/10824697 */}
>
>         {Array.from({ length: 5 }, (_, index) => {
>           return (
>             <span key={index}>
>               {stars >= index + 1 ? (
>                 <BsStarFill />
>               ) : stars >= index + 0.5 ? (
>                 <BsStarHalf />
>               ) : (
>                 <BsStar />
>               )}
>             </span>
>           )
>         })}
>       </div>
>       <div className="reviews">({reviews} reviews) </div>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}

### 16. Single Product Cart

{% hint style="info" %}
25\_ecommerce\_app/src/pages/SingleProductPage.js

25\_ecommerce\_app/src/components/AddToCart.js

25\_ecommerce\_app/src/components/AmountButtons.js
{% endhint %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FIymSaoMTMMRQktUyHqtN%2Fimage.png?alt=media&#x26;token=be33abbd-8910-484e-8c7e-a56b6f71fe82" alt=""><figcaption></figcaption></figure>

#### Color

After adding color selection, this is how it looks

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2Fd1owhdfsofCOyT7B5bpD%2Fimage.png?alt=media&#x26;token=225c5ca4-05d5-40be-b5ac-b18e5669dda9" alt=""><figcaption></figcaption></figure>

At this point after adding colors the AddToCart looks like this

{% code title="components/AddToCart.js" %}

```javascript
import React, { useState } from 'react'
import styled from 'styled-components'
import { Link } from 'react-router-dom'
import { FaCheck } from 'react-icons/fa'
import { useCartContext } from '../context/cart_context'
import AmountButtons from './AmountButtons'

const AddToCart = ({ product }) => {
  const { id, stock, colors } = product
  // state values to display selected color of the product. We can keep the state here as it is local to the product and don't need to bring it from the context
  const [mainColor, setMainColor] = useState(colors[0]) // setting first color as main color

  return (
    <Wrapper>
      <div className="colors">
        <span>colors : </span>
        <div>
          {colors.map((color, index) => {
            return (
              <button
                key={index}
                // adding active class on selected button. All buttons by default have 0.5 opacity, but active class has opacity of 1. Check css below in this file
                className={`color-btn ${mainColor === color && 'active'}`}
                style={{ backgroundColor: color }}
                onClick={() => setMainColor(color)}
              >
                {mainColor === color && <FaCheck />}
              </button>
            )
          })}
        </div>
      </div>
      <div className="btn-container"></div>
    </Wrapper>
  )
}
```

{% endcode %}

#### Cart Buttons

Let's now add cart increase and decrease functionality

```javascript
// Since cart increase and decrease are local to this component, AddToCart,
// let's add handler functions here and pass them one level down into AmountButtons component
  const increase = () => {
    setAmount((oldAmount) => {
      let tempAmount = oldAmount + 1
      // check if amount increased is in stock or not
      if (tempAmount > stock) {
        tempAmount = stock
      }
      return tempAmount
    })
  }

  const decrease = () => {
    setAmount((oldAmount) => {
      let tempAmount = oldAmount - 1
      // check if amount increased is in stock or not
      if (tempAmount < 1) {
        tempAmount = 1
      }
      return tempAmount
    })
  }
```

> Full Code
>
> {% code title="components/AddToCart.js" %}
>
> ```javascript
> import React, { useState } from 'react'
> import styled from 'styled-components'
> import { Link } from 'react-router-dom'
> import { FaCheck } from 'react-icons/fa'
> import { useCartContext } from '../context/cart_context'
> import AmountButtons from './AmountButtons'
>
> const AddToCart = ({ product }) => {
>   const { id, stock, colors } = product
>   // state values to display selected color of the product. We can keep the state here as it is local to the product and don't need to bring it from the context
>   const [mainColor, setMainColor] = useState(colors[0]) // setting first color as main color
>
>   // state to keep track of selected number of items in cart
>   const [amount, setAmount] = useState(1) // By default no sense in having 0 items so let's do 1
>
>   // Since cart increase and decrease are local to this component, let's add handler functions here and pass them one level down into AmountButtons component
>   const increase = () => {
>     setAmount((oldAmount) => {
>       let tempAmount = oldAmount + 1
>       // check if amount increased is in stock or not
>       if (tempAmount > stock) {
>         tempAmount = stock
>       }
>       return tempAmount
>     })
>   }
>
>   const decrease = () => {
>     setAmount((oldAmount) => {
>       let tempAmount = oldAmount - 1
>       // check if amount increased is in stock or not
>       if (tempAmount < 1) {
>         tempAmount = 1
>       }
>       return tempAmount
>     })
>   }
>
>   return (
>     <Wrapper>
>       <div className="colors">
>         <span>colors : </span>
>         <div>
>           {colors.map((color, index) => {
>             return (
>               <button
>                 key={index}
>                 // adding active class on selected button. All buttons by default have 0.5 opacity, but active class has opacity of 1. Check css below in this file
>                 className={`color-btn ${mainColor === color && 'active'}`}
>                 style={{ backgroundColor: color }}
>                 onClick={() => setMainColor(color)}
>               >
>                 {mainColor === color && <FaCheck />}
>               </button>
>             )
>           })}
>         </div>
>       </div>
>       <div className="btn-container">
>         <AmountButtons
>           amount={amount}
>           increase={increase}
>           decrease={decrease}
>         />
>         <Link to="/cart" className="btn">
>           add to cart
>         </Link>
>       </div>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}
>
> {% code title="components/AmountButtons.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { FaPlus, FaMinus } from 'react-icons/fa'
>
> const AmountButtons = ({ increase, decrease, amount }) => {
>   return (
>     <Wrapper className="amount-btns">
>       <button type="button" className="amount-btn" onClick={decrease}>
>         <FaMinus />
>       </button>
>       <h2>{amount}</h2>
>       <button type="button" className="amount-btn" onClick={increase}>
>         <FaPlus />
>       </button>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}
>
> {% code title="pages/SingleProductPage.js" %}
>
> ```javascript
> import React, { useEffect } from 'react'
> import { useParams, useHistory } from 'react-router-dom'
> import { useProductsContext } from '../context/products_context'
> import { single_product_url as url } from '../utils/constants'
> import { formatPrice } from '../utils/helpers'
> import {
>   Loading,
>   Error,
>   ProductImages,
>   AddToCart,
>   Stars,
>   PageHero,
> } from '../components'
> import styled from 'styled-components'
> import { Link } from 'react-router-dom'
> import { useCallback } from 'react'
>
> const SingleProductPage = () => {
>   const {
>     fetchSingleProduct,
>     single_product_loading: loading,
>     single_product_error: error,
>     single_product: product,
>   } = useProductsContext()
>
>   const { id } = useParams()
>   const history = useHistory()
>
>   useEffect(() => {
>     fetchSingleProduct(`${url}${id}`)
>   }, [fetchSingleProduct, id])
>
>   // we will navigate back to home screen after 3 sec if there is an error in fetching single product
>   // To test this, change single product url and you should see this behaviour
>   useEffect(() => {
>     if (error) {
>       setTimeout(() => {
>         history.push('/')
>       }, 3000)
>     }
>   }, [error, history])
>
>   if (loading) {
>     return <Loading />
>   }
>   if (error) {
>     return <Error />
>   }
>   const {
>     id: sku,
>     stock,
>     price,
>     shipping,
>     featured,
>     colors,
>     category,
>     images,
>     reviews,
>     stars,
>     name,
>     description,
>     company,
>   } = product
>   console.log(product)
>
>   return (
>     <Wrapper>
>       <PageHero title={name} product />
>       <div className="section section-center page">
>         <Link to="/products" className="btn">
>           Back to products
>         </Link>
>         <div className="product-center">
>           <ProductImages images={images} />
>           <section className="content">
>             <h2>{name}</h2>
>             <Stars stars={stars} reviews={reviews} />
>             <h5 className="price">{formatPrice(price)}</h5>
>             <p className="desc">{description}</p>
>             <p className="info">
>               <span>Available : </span>
>               {stock > 0 ? 'In stock' : 'Out of stock'}
>             </p>
>             <p className="info">
>               <span>SKU : </span>
>               {sku}
>             </p>
>             <p className="info">
>               <span>Brand : </span>
>               {company}
>             </p>
>             <hr />
>             {stock > 0 && <AddToCart product={product} />}
>           </section>
>         </div>
>       </div>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}

The `SingleProductPage` looks this way till now

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2F2AmJxkD5jERrWnodymil%2Fimage.png?alt=media&#x26;token=9571010b-82b9-406c-b882-b5accd4328c8" alt=""><figcaption></figcaption></figure>

### 17. Filter context in Product Page

{% hint style="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
{% endhint %}

**NOT ADDING UI BUT JUST THE FUNCTIONALITY OF FILTERING HERE**

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FXPRh3WxvmcHloW2GtTQ9%2Fimage.png?alt=media&#x26;token=03a55f65-5cb4-4000-8e77-3c5d0c2eb591" alt=""><figcaption></figcaption></figure>

{% hint style="warning" %}
We can technically jam everything is product context but it would later be difficult to manage as the context would have filtering, searching, switch between list and grid view and so on. So it would be better to add filter functionality in a separate context. So let's do it in filter context.
{% endhint %}

{% hint style="warning" %}
Also note that we have a clear filter option here. When user starts filtering the product based on different filters, the products get narrowed and we see only fewer products on screen. Then when we need to get back all the products as before, we need to clear the filters. So we need two instances of products. One for the filtered one and the other one is shown in its original state after clearing the filters
{% endhint %}

#### So this is the idea we are going to focus on

* We loaded products in products context initially like this

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FqgalEjv2beOThw7fK54s%2Fimage.png?alt=media&#x26;token=f04702a0-fac9-458f-8103-2919580e69d3" alt=""><figcaption></figcaption></figure>

* 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?

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FfmSwfXaBekxt69DlCJ4v%2Fimage.png?alt=media&#x26;token=939cca45-cb81-4d11-b4d3-eb82430e890a" alt=""><figcaption></figcaption></figure>

* 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.&#x20;
* 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).&#x20;
* 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. &#x20;

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

{% code title="context/filter\_context.js" %}

```javascript
// see we have a state for filtered_products and all_products as explained below
const initialState = {
  filtered_products: [],
  all_products: [],
}

const FilterContext = React.createContext()

export const FilterProvider = ({ children }) => {
  const [state, dispatch] = useContext(reducer, initialState)
  
  return (
    <FilterContext.Provider value="filter context">
      {children}
    </FilterContext.Provider>
  )
}
// make sure use
export const useFilterContext = () => {
  return useContext(FilterContext)
}
```

{% endcode %}

Now how do we get the products from products\_context to sset into filtered\_products and all\_products. Here's how we get

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FbD4f0lDO1saMw09lk4N4%2Fimage.png?alt=media&#x26;token=90c6eeb9-9c1b-4126-91a1-fd51fb672914" alt=""><figcaption></figcaption></figure>

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

{% code title="index.js" %}

```javascript
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import App from './App'

import { ProductsProvider } from './context/products_context'
import { FilterProvider } from './context/filter_context'
import { CartProvider } from './context/cart_context'
import { UserProvider } from './context/user_context'
import { Auth0Provider } from '@auth0/auth0-react'

const root = ReactDOM.createRoot(document.getElementById('root'))

root.render(
  <ProductsProvider>
    <App />
  </ProductsProvider>
)
```

{% endcode %}

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FcSrW0sMMDKDjaKdhRt2F%2Fimage.png?alt=media&#x26;token=ec9102af-738e-4d56-a85f-5b20c9a07cb1" alt=""><figcaption></figcaption></figure>

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FDINDiNUGwGj1lNKvkBkz%2Fimage.png?alt=media&#x26;token=d786a2e0-f813-4772-9514-6db2a836a0f4" alt=""><figcaption></figcaption></figure>

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.&#x20;

{% hint style="warning" %}
Note that if you uncomment the products code in filter context this would work which proves the point regarding the error above.&#x20;

```javascript
  const { products } = useProductsContext()
  console.log('The products are', products)
```

{% endhint %}

Let's do the right setup now

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FUyU8JAZEUPlhuJFdRUBR%2Fimage.png?alt=media&#x26;token=8d685db4-528e-4f5a-9692-d070ef29fab9" alt=""><figcaption></figcaption></figure>

This would now solve the error and filter\_context now gets the products from products context

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2F3cwnGqBoOYR63qr9DYWz%2Fimage.png?alt=media&#x26;token=df5f95b1-db53-47ae-bf37-654443c5c612" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FPoqQVaIJh8P8BKdkbuH3%2Fimage.png?alt=media&#x26;token=802fdea4-bfa8-40ce-ba5c-2f8fa67f6455" alt=""><figcaption></figcaption></figure>

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FBYJw2aLVWcuDaCXCcskY%2Fimage.png?alt=media&#x26;token=553b6bf0-e6c4-4171-b1cf-0225e2aa7e3a" alt=""><figcaption></figcaption></figure>

filter\_reducer looks like this

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FfeVmo6dBGRfrsbPuPuNv%2Fimage.png?alt=media&#x26;token=ae2dd080-8e31-4084-8641-ac6b66e38f81" alt=""><figcaption></figcaption></figure>

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2F1MtuFCACh8HCsKxNRMeu%2Fimage.png?alt=media&#x26;token=41a0b728-15d8-4822-af88-52c13dee7d41" alt=""><figcaption></figcaption></figure>

> Full Code
>
> {% code title="index.js" %}
>
> ```javascript
> import React from 'react'
> import ReactDOM from 'react-dom/client'
> import './index.css'
> import App from './App'
>
> import { ProductsProvider } from './context/products_context'
> import { FilterProvider } from './context/filter_context'
> import { CartProvider } from './context/cart_context'
> import { UserProvider } from './context/user_context'
> import { Auth0Provider } from '@auth0/auth0-react'
>
> const root = ReactDOM.createRoot(document.getElementById('root'))
>
> root.render(
>   <ProductsProvider>
>     <FilterProvider>
>       <App />
>     </FilterProvider>
>   </ProductsProvider>
> )
>
> ```
>
> {% endcode %}
>
> {% code title="context/filter\_context.js" %}
>
> ```javascript
> import React, { useEffect, useContext, useReducer } from 'react'
> import reducer from '../reducers/filter_reducer'
> import {
>   LOAD_PRODUCTS,
>   SET_GRIDVIEW,
>   SET_LISTVIEW,
>   UPDATE_SORT,
>   SORT_PRODUCTS,
>   UPDATE_FILTERS,
>   FILTER_PRODUCTS,
>   CLEAR_FILTERS,
> } from '../actions'
> import { useProductsContext } from './products_context'
>
> const initialState = {
>   filtered_products: [],
>   all_products: [],
> }
>
> const FilterContext = React.createContext()
>
> export const FilterProvider = ({ children }) => {
>   const [state, dispatch] = useReducer(reducer, initialState)
>   const { products } = useProductsContext()
>   // console.log('The products are', products)
>
>   useEffect(() => {
>     dispatch({ type: LOAD_PRODUCTS, payload: products })
>   }, [products])
>
>   return (
>     <FilterContext.Provider value="filter context">
>       {children}
>     </FilterContext.Provider>
>   )
> }
> // make sure use
> export const useFilterContext = () => {
>   return useContext(FilterContext)
> }
>
> ```
>
> {% endcode %}
>
> {% code title="reducers/filter\_reducer.js" %}
>
> ```javascript
> import {
>   LOAD_PRODUCTS,
>   SET_LISTVIEW,
>   SET_GRIDVIEW,
>   UPDATE_SORT,
>   SORT_PRODUCTS,
>   UPDATE_FILTERS,
>   FILTER_PRODUCTS,
>   CLEAR_FILTERS,
> } from '../actions'
>
> const filter_reducer = (state, action) => {
>   if (action.type === LOAD_PRODUCTS) {
>     return {
>       ...state,
>       all_products: [...action.payload], // spreading out values are extemely important here so that we are deep copying now. In that way both all_products and filtered_products point to different memory location. During filtering the products we just modify the filtered_products and don't touch all_products
>       filtered_products: [...action.payload],
>     }
>   }
>   throw new Error(`No Matching "${action.type}" - action type`)
> }
>
> export default filter_reducer
>
> ```
>
> {% endcode %}

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

{% hint style="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
{% endhint %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FKZUOvu6Px56Iy5WtFPGq%2Fimage.png?alt=media&#x26;token=73432caa-406d-44e4-bd08-3510767681b2" alt=""><figcaption></figcaption></figure>

{% code title="pages/ProductsPage.js" %}

```javascript
import React from 'react'
import styled from 'styled-components'
import { Filters, ProductList, Sort, PageHero } from '../components'

const ProductsPage = () => {
  return (
    <main>
      <PageHero title="products" />
      <Wrapper className="page">
        <div className="section-center products">
          <Filters />
          <div>
            <Sort />
            <ProductList />
          </div>
        </div>
      </Wrapper>
    </main>
  )
}
```

{% endcode %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FLrLdRrAgwjjxNA8cs61m%2Fimage.png?alt=media&#x26;token=414fe8e9-ad7c-46f7-95e8-8020efbd5627" alt=""><figcaption></figcaption></figure>

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.&#x20;

```javascript
export const FilterProvider = ({ children }) => {
  const [state, dispatch] = useReducer(reducer, initialState)
  const { products } = useProductsContext()
  // console.log('The products are', products)

  useEffect(() => {
    dispatch({ type: LOAD_PRODUCTS, payload: products })
  }, [products])

  return (
  // this is the products we will get in ProductsList
    <FilterContext.Provider value={{...state}}>
      {children}
    </FilterContext.Provider>
  )
}
```

The productsList currently looks like this&#x20;

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FiKWwX3CFeqQboxGEMf07%2Fimage.png?alt=media&#x26;token=bfd32136-4ba4-4df0-b0cd-c36eb208c9bf" alt=""><figcaption></figcaption></figure>

The ProductsList code looks like this

{% code title="src/components/ProductsList.js" %}

```javascript
import React from 'react'
import { useFilterContext } from '../context/filter_context'
import GridView from './GridView'
import ListView from './ListView'

const ProductList = () => {
  const { filtered_products: products } = useFilterContext()

  return <GridView products={products}>product list</GridView>
}

export default ProductList

```

{% endcode %}

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`

```javascript
const initialState = {
  filtered_products: [],
  all_products: [],
  grid_view: false,
}
```

And we use it in ProductsList like this&#x20;

```javascript
import React from 'react'
import { useFilterContext } from '../context/filter_context'
import GridView from './GridView'
import ListView from './ListView'

const ProductList = () => {
  const { filtered_products: products, grid_view } = useFilterContext()

  if (products.length < 1) {
    return (
      <h5 style={{ textTransform: 'none' }}>
        Sorry, no products matching your search
      </h5>
    )
  }
  if (!grid_view) {
    return <ListView products={products} />
  }

  return <GridView products={products}>product list</GridView>
}

export default ProductList

```

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FvbBmfu7rnguNOb9eoQl6%2Fimage.png?alt=media&#x26;token=22b84687-f019-49a4-82e3-fcbdd6e1aec4" alt=""><figcaption></figcaption></figure>

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2Fbv7G2GWsP0yuHNGvMa7i%2Fimage.png?alt=media&#x26;token=8f43dfdf-7fe3-4db4-b671-c064453d281d" alt=""><figcaption></figcaption></figure>

> Full Code
>
> {% code title="context/filter\_context.js" %}
>
> ```javascript
> import React, { useEffect, useContext, useReducer } from 'react'
> import reducer from '../reducers/filter_reducer'
> import {
>   LOAD_PRODUCTS,
>   SET_GRIDVIEW,
>   SET_LISTVIEW,
>   UPDATE_SORT,
>   SORT_PRODUCTS,
>   UPDATE_FILTERS,
>   FILTER_PRODUCTS,
>   CLEAR_FILTERS,
> } from '../actions'
> import { useProductsContext } from './products_context'
>
> const initialState = {
>   filtered_products: [],
>   all_products: [],
>   grid_view: true,
> }
>
> const FilterContext = React.createContext()
>
> export const FilterProvider = ({ children }) => {
>   const [state, dispatch] = useReducer(reducer, initialState)
>   const { products } = useProductsContext()
>   // console.log('The products are', products)
>
>   useEffect(() => {
>     dispatch({ type: LOAD_PRODUCTS, payload: products })
>   }, [products])
>
>   return (
>     <FilterContext.Provider value={{ ...state }}>
>       {children}
>     </FilterContext.Provider>
>   )
> }
> // make sure use
> export const useFilterContext = () => {
>   return useContext(FilterContext)
> }
> ```
>
> {% endcode %}
>
> {% code title="pages/ProductsPage.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { Filters, ProductList, Sort, PageHero } from '../components'
>
> const ProductsPage = () => {
>   return (
>     <main>
>       <PageHero title="products" />
>       <Wrapper className="page">
>         <div className="section-center products">
>           <Filters />
>           <div>
>             // WE WILL CODE THIS SORT IN NEXT STEP
>             <Sort />
>             <ProductList />
>           </div>
>         </div>
>       </Wrapper>
>     </main>
>   )
> }
> ```
>
> {% endcode %}
>
> {% code title="components/ProductsList.js" %}
>
> ```javascript
> import React from 'react'
> import { useFilterContext } from '../context/filter_context'
> import GridView from './GridView'
> import ListView from './ListView'
>
> const ProductList = () => {
>   const { filtered_products: products, grid_view } = useFilterContext()
>
>   if (products.length < 1) {
>     return (
>       <h5 style={{ textTransform: 'none' }}>
>         Sorry, no products matching your search
>       </h5>
>     )
>   }
>   if (!grid_view) {
>     return <ListView products={products} />
>   }
>
>   return <GridView products={products}>product list</GridView>
> }
>
> export default ProductList
> ```
>
> {% endcode %}
>
> {% code title="components/GridView\.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import Product from './Product'
>
> const GridView = ({ products }) => {
>   return (
>     <Wrapper>
>       <div className="products-container">
>         {products.map((product) => {
>           return <Product key={product.id} {...product} />
>         })}
>       </div>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}
>
> {% code title="components/ListView\.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { formatPrice } from '../utils/helpers'
> import { Link } from 'react-router-dom'
> const ListView = ({ products }) => {
>   return (
>     <Wrapper>
>       {products.map((product) => {
>         const { id, image, name, price, description } = product
>         return (
>           <article key={id}>
>             <img src={image} alt={name} />
>             <div>
>               <h4>{name}</h4>
>               <h5 className="price">{formatPrice(price)}</h5>
>               <p>{description.substring(0, 150)}...</p>
>               <Link className="btn" to={`/products/${id}`}>
>                 Details
>               </Link>
>             </div>
>           </article>
>         )
>       })}
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}

### 19. Sort UI - Products Page

{% hint style="info" %}
25\_ecommerce\_app/src/components/Sort.js
{% endhint %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2Fhi5aMzjn68sgzJBvCQUU%2Fimage.png?alt=media&#x26;token=a45fb5be-6ad3-4e9e-8fa0-dcc0385d0e50" alt=""><figcaption><p>expected sort UI</p></figcaption></figure>

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
>
> {% code title="components/Sort.js" %}
>
> ```javascript
> import React from 'react'
> import { useFilterContext } from '../context/filter_context'
> import { BsFillGridFill, BsList } from 'react-icons/bs'
> import styled from 'styled-components'
> const Sort = () => {
>   const { filtered_products: products, grid_view } = useFilterContext()
>   return (
>     <Wrapper>
>       <div className="btn-container">
>         <button type="button" className={`${grid_view && 'active'}`}>
>           <BsFillGridFill />
>         </button>
>         <button type="button" className={`${!grid_view && 'active'}`}>
>           <BsList />
>         </button>
>       </div>
>       <p>{products.length} products found</p>
>       <hr />
>       <form>
>         <label htmlFor="sort">sort by</label>
>         <select name="sort" id="sort" className="sort-input">
>           <option value="price-lowest">price (lowest)</option>
>           <option value="price-highest">price (highest)</option>
>           <option value="name-a">name (a-z)</option>
>           <option value="name-z">name (z-a)</option>
>         </select>
>       </form>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}

### 20. Sort Functionality - Products Page

{% hint style="info" %}
25\_ecommerce\_app/src/context/filter\_context.js

25\_ecommerce\_app/src/reducers/filter\_reducer.js
{% endhint %}

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FIQs2caWzwmtOBW0OCT2O%2Fimage.png?alt=media&#x26;token=67003a48-d795-41f5-b2a1-4beb6dc7f3d6" alt=""><figcaption></figcaption></figure>

#### 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`

{% code title="filter\_context.js" %}

```javascript
  const setGridView = () => {
    dispatch({ type: SET_GRIDVIEW })
  }

  const setListView = () => {
    dispatch({ type: SET_LISTVIEW })
  }
```

{% endcode %}

{% code title="filter\_reducer.js" %}

```javascript
  if (action.type === SET_GRIDVIEW) {
    return {
      ...state,
      grid_view: true,
    }
  }

  if (action.type === SET_LISTVIEW) {
    return {
      ...state,
      grid_view: false,
    }
  }
```

{% endcode %}

{% code title="components/Sort.js" %}

```javascript
return (
    <Wrapper>
      <div className="btn-container">
        <button
          type="button"
          onClick={setGridView}
          className={`${grid_view && 'active'}`}
        >
          <BsFillGridFill />
        </button>
        <button
          type="button"
          onClick={setListView}
          className={`${!grid_view && 'active'}`}
        >
          <BsList />
        </button>
      </div>
      <p>{products.length} products found</p>
      <hr />
      <form>
        <label htmlFor="sort">sort by</label>
        <select name="sort" id="sort" className="sort-input">
          <option value="price-lowest">price (lowest)</option>
          <option value="price-highest">price (highest)</option>
          <option value="name-a">name (a-z)</option>
          <option value="name-z">name (z-a)</option>
        </select>
      </form>
    </Wrapper>
```

{% endcode %}

#### 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.&#x20;

{% code title="compoents/Sort.js" %}

````javascript
  <select
    name="sort"
    id="sort"
    className="sort-input"
    value={sort}
    onChange={updateSort}
  >```javascript
 // updateSort triggers when we change the select input
  const updateSort = (e) => {
    // const name = e.target.name // for demonstration. We will use this later
    const value = e.target.value
    dispatch({ type: UPDATE_SORT, payload: value })
  }
```
    <option value="price-lowest">price (lowest)</option>
    <option value="price-highest">price (highest)</option>
    <option value="name-a">name (a-z)</option>
    <option value="name-z">name (z-a)</option>
  </select>
````

{% endcode %}

{% code title="context/filter\_context.js" %}

```javascript
 // updateSort triggers when we change the select input
  const updateSort = (e) => {
    // const name = e.target.name // for demonstration. We will use this later
    const value = e.target.value
    dispatch({ type: UPDATE_SORT, payload: value })
  }
```

{% endcode %}

{% code title="reducer/filter\_reducer.js" %}

```javascript
  if (action.type === UPDATE_SORT) {
    return {
      ...state,
      sort: action.payload,
    }
  }
```

{% endcode %}

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

```javascript

  // when sort is clicked, first the updateSort is 
  // called and then  the below useEffect will run to 
  // change the products as per the sort
  useEffect(() => {
    dispatch({ type: SORT_PRODUCTS })
  }, [products, state.sort])
```

reducer for this `SORT_PRODUCTS` looks this way&#x20;

```javascript
  if (action.type === SORT_PRODUCTS) {
    const { sort, filtered_products } = state
    let tempProducts = [...filtered_products]
    if (sort === 'price-lowest') {
      tempProducts = tempProducts.sort((a, b) => a.price - b.price)
    }
    if (sort === 'price-highest') {
      tempProducts = tempProducts.sort((a, b) => b.price - a.price)
    }
    if (sort === 'name-a') {
      tempProducts = tempProducts.sort((a, b) => {
        return a.name.localeCompare(b.name)
      })
    }
    if (sort === 'name-z') {
      tempProducts = tempProducts.sort((a, b) => {
        return b.name.localeCompare(a.name)
      })
    }
    return {
      ...state,
      filtered_products: tempProducts,
    }
  }
```

Now all 4 sort functionalities work as expected.

> Full code
>
> {% code title="context/filter\_context.js" %}
>
> ```javascript
> import React, { useEffect, useContext, useReducer } from 'react'
> import reducer from '../reducers/filter_reducer'
> import {
>   LOAD_PRODUCTS,
>   SET_GRIDVIEW,
>   SET_LISTVIEW,
>   UPDATE_SORT,
>   SORT_PRODUCTS,
>   UPDATE_FILTERS,
>   FILTER_PRODUCTS,
>   CLEAR_FILTERS,
> } from '../actions'
> import { useProductsContext } from './products_context'
>
> const initialState = {
>   filtered_products: [],
>   all_products: [],
>   grid_view: true,
>   sort: 'price-lowest',
> }
>
> const FilterContext = React.createContext()
>
> export const FilterProvider = ({ children }) => {
>   const [state, dispatch] = useReducer(reducer, initialState)
>   const { products } = useProductsContext()
>
>   const setGridView = () => {
>     dispatch({ type: SET_GRIDVIEW })
>   }
>
>   const setListView = () => {
>     dispatch({ type: SET_LISTVIEW })
>   }
>
>   // updateSort triggers when we change the select input
>   const updateSort = (e) => {
>     // const name = e.target.name // for demonstration. We will use this later
>     const value = e.target.value
>     dispatch({ type: UPDATE_SORT, payload: value })
>   }
>
>   // when sort is clicked, first the updateSort is called and then  the below useEffect will run to change the products as per the sort
>   useEffect(() => {
>     dispatch({ type: SORT_PRODUCTS })
>   }, [products, state.sort])
>
>   useEffect(() => {
>     dispatch({ type: LOAD_PRODUCTS, payload: products })
>   }, [products])
>
>   return (
>     <FilterContext.Provider
>       value={{ ...state, setGridView, setListView, updateSort }}
>     >
>       {children}
>     </FilterContext.Provider>
>   )
> }
> // make sure use
> export const useFilterContext = () => {
>   return useContext(FilterContext)
> }
>
> ```
>
> {% endcode %}
>
> {% code title="reducers/filter\_reducer.js" %}
>
> ```javascript
> import {
>   LOAD_PRODUCTS,
>   SET_LISTVIEW,
>   SET_GRIDVIEW,
>   UPDATE_SORT,
>   SORT_PRODUCTS,
>   UPDATE_FILTERS,
>   FILTER_PRODUCTS,
>   CLEAR_FILTERS,
> } from '../actions'
>
> const filter_reducer = (state, action) => {
>   if (action.type === LOAD_PRODUCTS) {
>     return {
>       ...state,
>       all_products: [...action.payload], // spreading out values are extemely important here so that we are deep copying now. In that way both all_products and filtered_products point to different memory location. During filtering the products we just modify the filtered_products and don't touch all_products
>       filtered_products: [...action.payload],
>     }
>   }
>
>   if (action.type === SET_GRIDVIEW) {
>     return {
>       ...state,
>       grid_view: true,
>     }
>   }
>
>   if (action.type === SET_LISTVIEW) {
>     return {
>       ...state,
>       grid_view: false,
>     }
>   }
>
>   if (action.type === UPDATE_SORT) {
>     return {
>       ...state,
>       sort: action.payload,
>     }
>   }
>
>   if (action.type === SORT_PRODUCTS) {
>     const { sort, filtered_products } = state
>     let tempProducts = [...filtered_products]
>     if (sort === 'price-lowest') {
>       tempProducts = tempProducts.sort((a, b) => a.price - b.price)
>     }
>     if (sort === 'price-highest') {
>       tempProducts = tempProducts.sort((a, b) => b.price - a.price)
>     }
>     if (sort === 'name-a') {
>       tempProducts = tempProducts.sort((a, b) => {
>         return a.name.localeCompare(b.name)
>       })
>     }
>     if (sort === 'name-z') {
>       tempProducts = tempProducts.sort((a, b) => {
>         return b.name.localeCompare(a.name)
>       })
>     }
>     return {
>       ...state,
>       filtered_products: tempProducts,
>     }
>   }
>
>   throw new Error(`No Matching "${action.type}" - action type`)
> }
>
> export default filter_reducer
>
> ```
>
> {% endcode %}

### 21. Filters - Product Page

{% hint style="info" %}
25\_ecommerce\_app/src/context/filter\_context.js

25\_ecommerce\_app/src/reducers/filter\_reducer.js

25\_ecommerce\_app/src/components/Filters.js
{% endhint %}

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FzyXt5M2z6EAayFuzimwI%2Fimage.png?alt=media&#x26;token=3d46e7d9-f19e-43f3-bc09-cee662caef52" alt=""><figcaption></figcaption></figure>

We completed sorting, let's now work on the left part filters.&#x20;

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

```javascript
const initialState = {
  filtered_products: [],
  all_products: [],
  grid_view: true,
  sort: 'price-lowest',
  filters: {
    text: '',
    category: 'all',
    company: 'all',
    color: 'all',
    min_price: 0,
    max_price: 0, // this is price of the highest priced product
    price: 0,
    shipping: false,
  },
}
```

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.

```javascript
if (action.type === LOAD_PRODUCTS) {
    // 1st way to get max price from products array
    /*
    const maxPricedProduct = [...action.payload].reduce((acc, cur) => {
      if (acc.price > cur.price) {
        cur = acc
      }
      return cur
    }, 0) 
     */

    // 2nd way to get max price from products array
    let maxPrice = [...action.payload].map((p) => p.price) // price array
    maxPrice = Math.max(...maxPrice)

    return {
      ...state,
      all_products: [...action.payload], // spreading out values are extemely important here so that we are deep copying now. In that way both all_products and filtered_products point to different memory location. During filtering the products we just modify the filtered_products and don't touch all_products
      filtered_products: [...action.payload],
      filters: {
        ...state.filters,
        max_price: maxPrice,
        price: maxPrice,
      },
    }
  }
```

&#x20;

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FKOjUDA0ndaSxjoUQ3mP7%2Fimage.png?alt=media&#x26;token=93fbb376-04a0-4869-b54b-9ee05d957dee" alt=""><figcaption></figcaption></figure>

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)

{% code title="context/filter\_context" %}

```javascript
  // when filters are changed this single function updateFilters is invoked - Similar to updateSort
  const updateFilters = (e) => {

  }
  
  const clearFilters = () => {

  }

```

{% endcode %}

This is what our Filter component looks currently

{% code title="components/Filter.js" %}

```javascript
import React from 'react'
import styled from 'styled-components'
import { useFilterContext } from '../context/filter_context'
import { getUniqueValues, formatPrice } from '../utils/helpers'
import { FaCheck } from 'react-icons/fa'

const Filters = () => {
  const {
    filters: {
      text,
      category,
      company,
      color,
      min_price,
      max_price,
      price,
      shipping,
    },
    updatedFilters,
    clearFilters,
    all_products,
  } = useFilterContext()

  return (
    <Wrapper>
      <div className="content">
        <form onSubmit={(e) => e.preventDefault()}>
          {/* search input */}
          <div className="form-control">
            <input
              type="text"
              name="text"
              placeholder="search"
              className="search-input"
              value={text}
              onChange={updatedFilters}
            />
          </div>
          {/* end of search input */}
        </form>
      </div>
    </Wrapper>
  )
}

export default Filters

```

{% endcode %}

For this now our `updateFilters` looks like this&#x20;

```javascript
// when filters are changed this single function updateFilters is invoked - Similar to updateSort
const updateFilters = (e) => {
  let name = e.target.name
  let value = e.target.value
  dispatch({
    type: UPDATE_FILTERS,
    payload: { name: name, value: value },
  })
}
```

And for this the filter reducer looks like this&#x20;

```javascript
  // FILTERS REDUCER PART

  if (action.type === UPDATE_FILTERS) {
    const { name, value } = action.payload
    return {
      ...state,
      filters: {
        ...state.filters,
        [name]: value,
      },
    }
  }
```

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FZ9lvgOhC6yq7hk0PLQjp%2Fimage.png?alt=media&#x26;token=8ab2622b-fdfb-45a2-9a65-e946569ad252" alt=""><figcaption></figcaption></figure>

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

```javascript
  // when the filters change (for example when user types 
  // something in search), the updateFilters is called which sets the 
  // state of that filter. Once the filter is set, we need to fetch the 
  // products for that search, hence this below useEffect
  
  useEffect(() => {
    dispatch({ type: FILTER_PRODUCTS })
  }, [products, state.filters])
```

Along with previously written useEffect of sort it looks this way

```javascript
  // when the filters change (for example when user types 
  // something in search), 
  // the updateFilters is called which sets the state of that filter. 
  // Once the filter is set, we need to fetch the products for that search, 
  // hence this below useEffect
  
  useEffect(() => {
    dispatch({ type: FILTER_PRODUCTS })
  }, [products, state.filters])

  // when sort is clicked, first the updateSort is called and then  the below 
  // useEffect will run to change the products as per the sort
  
  useEffect(() => {
    dispatch({ type: SORT_PRODUCTS })
  }, [products, state.sort])

  /* NOTE THAT : we could have combined ABOVE TWO useEffects for sort and filters 
  into one and add two dispatches into that, but I prefer keeping them 
  independent. BUT POINT IS, FIRST WE HAVE TO FILTER THE PRODUCTS AND THEN ONLY 
  SORT THEM. HENCE WE NEED TO PLACE FILTER dispatch FIRST and then SORT dispatch 
  like we have done above*/
```

> Full Code
>
> {% code title="context/filter\_context.js" %}
>
> ```javascript
> import React, { useEffect, useContext, useReducer } from 'react'
> import reducer from '../reducers/filter_reducer'
> import {
>   LOAD_PRODUCTS,
>   SET_GRIDVIEW,
>   SET_LISTVIEW,
>   UPDATE_SORT,
>   SORT_PRODUCTS,
>   UPDATE_FILTERS,
>   FILTER_PRODUCTS,
>   CLEAR_FILTERS,
> } from '../actions'
> import { useProductsContext } from './products_context'
>
> const initialState = {
>   filtered_products: [],
>   all_products: [],
>   grid_view: true,
>   sort: 'price-lowest',
>   filters: {
>     text: '',
>     category: 'all',
>     company: 'all',
>     color: 'all',
>     min_price: 0,
>     max_price: 0, // this is price of the highest priced product
>     price: 0,
>     shipping: false,
>   },
> }
>
> const FilterContext = React.createContext()
>
> export const FilterProvider = ({ children }) => {
>   const [state, dispatch] = useReducer(reducer, initialState)
>   const { products } = useProductsContext()
>
>   const setGridView = () => {
>     dispatch({ type: SET_GRIDVIEW })
>   }
>
>   const setListView = () => {
>     dispatch({ type: SET_LISTVIEW })
>   }
>
>   // updateSort triggers when we change the select input
>   const updateSort = (e) => {
>     // const name = e.target.name // for demonstration. We will use this later
>     const value = e.target.value
>     dispatch({ type: UPDATE_SORT, payload: value })
>   }
>
>   // when filters are changed this single function updateFilters is invoked - Similar to updateSort
>   const updateFilters = (e) => {
>     let name = e.target.name
>     let value = e.target.value
>     dispatch({
>       type: UPDATE_FILTERS,
>       payload: { name: name, value: value },
>     })
>   }
>
>   const clearFilters = () => {}
>
>   // when the filters change (for example when user types something in search), the updateFilters is called which sets the state of that filter. Once the filter is set, we need to fetch the products for that search, hence this below useEffect
>   useEffect(() => {
>     dispatch({ type: FILTER_PRODUCTS })
>   }, [products, state.filters])
>
>   // when sort is clicked, first the updateSort is called and then  the below useEffect will run to change the products as per the sort
>   useEffect(() => {
>     dispatch({ type: SORT_PRODUCTS })
>   }, [products, state.sort])
>
>   /* NOTE THAT : we could have combined ABOVE TWO useEffects for sort and filters into one and add two dispatches into that, but I prefer keeping them independent. BUT POINT IS, FIRST WE HAVE TO FILTER THE PRODUCTS AND THEN ONLY SORT THEM. HENCE WE NEED TO PLACE FILTER dispatch FIRST and then SORT dispatch like we have done above*/
>
>   // Load the products
>   useEffect(() => {
>     dispatch({ type: LOAD_PRODUCTS, payload: products })
>   }, [products])
>
>   return (
>     <FilterContext.Provider
>       value={{
>         ...state,
>         setGridView,
>         setListView,
>         updateSort,
>         updateFilters,
>         clearFilters,
>       }}
>     >
>       {children}
>     </FilterContext.Provider>
>   )
> }
> // make sure use
> export const useFilterContext = () => {
>   return useContext(FilterContext)
> }
>
> ```
>
> {% endcode %}
>
> {% code title="reducer/filter\_reducer.js" %}
>
> ```javascript
> import {
>   LOAD_PRODUCTS,
>   SET_LISTVIEW,
>   SET_GRIDVIEW,
>   UPDATE_SORT,
>   SORT_PRODUCTS,
>   UPDATE_FILTERS,
>   FILTER_PRODUCTS,
>   CLEAR_FILTERS,
> } from '../actions'
>
> const filter_reducer = (state, action) => {
>   if (action.type === LOAD_PRODUCTS) {
>     // 1st way to get max price from products array
>     /*
>     const maxPricedProduct = [...action.payload].reduce((acc, cur) => {
>       if (acc.price > cur.price) {
>         cur = acc
>       }
>       return cur
>     }, 0) 
>      */
>
>     // 2nd way to get max price from products array
>     let maxPrice = [...action.payload].map((p) => p.price) // price array
>     maxPrice = Math.max(...maxPrice)
>
>     return {
>       ...state,
>       all_products: [...action.payload], // spreading out values are extemely important here so that we are deep copying now. In that way both all_products and filtered_products point to different memory location. During filtering the products we just modify the filtered_products and don't touch all_products
>       filtered_products: [...action.payload],
>       filters: {
>         ...state.filters,
>         max_price: maxPrice,
>         price: maxPrice,
>       },
>     }
>   }
>
>   if (action.type === SET_GRIDVIEW) {
>     return {
>       ...state,
>       grid_view: true,
>     }
>   }
>
>   if (action.type === SET_LISTVIEW) {
>     return {
>       ...state,
>       grid_view: false,
>     }
>   }
>
>   // SORT
>
>   if (action.type === UPDATE_SORT) {
>     return {
>       ...state,
>       sort: action.payload,
>     }
>   }
>
>   if (action.type === SORT_PRODUCTS) {
>     const { sort, filtered_products } = state
>     let tempProducts = [...filtered_products]
>     if (sort === 'price-lowest') {
>       tempProducts = tempProducts.sort((a, b) => a.price - b.price)
>     }
>     if (sort === 'price-highest') {
>       tempProducts = tempProducts.sort((a, b) => b.price - a.price)
>     }
>     if (sort === 'name-a') {
>       tempProducts = tempProducts.sort((a, b) => {
>         return a.name.localeCompare(b.name)
>       })
>     }
>     if (sort === 'name-z') {
>       tempProducts = tempProducts.sort((a, b) => {
>         return b.name.localeCompare(a.name)
>       })
>     }
>     return {
>       ...state,
>       filtered_products: tempProducts,
>     }
>   }
>
>   // FILTERS
>
>   if (action.type === UPDATE_FILTERS) {
>     const { name, value } = action.payload
>     return {
>       ...state,
>       filters: {
>         ...state.filters,
>         [name]: value,
>       },
>     }
>   }
>
>   if (action.type === FILTER_PRODUCTS) {
>     return { ...state }
>   }
>
>   throw new Error(`No Matching "${action.type}" - action type`)
> }
>
> export default filter_reducer
>
> ```
>
> {% endcode %}
>
> {% code title="components/Filter.js" %}
>
> ```javascript
> import React from 'react'
> import styled from 'styled-components'
> import { useFilterContext } from '../context/filter_context'
> import { getUniqueValues, formatPrice } from '../utils/helpers'
> import { FaCheck } from 'react-icons/fa'
>
> const Filters = () => {
>   const {
>     filters: {
>       text,
>       category,
>       company,
>       color,
>       min_price,
>       max_price,
>       price,
>       shipping,
>     },
>     updateFilters,
>     clearFilters,
>     all_products,
>   } = useFilterContext()
>
>   return (
>     <Wrapper>
>       <div className="content">
>         <form onSubmit={(e) => e.preventDefault()}>
>           {/* search input */}
>           <div className="form-control">
>             <input
>               type="text"
>               name="text"
>               placeholder="search"
>               className="search-input"
>               value={text}
>               onChange={updateFilters}
>             />
>           </div>
>           {/* end of search input */}
>         </form>
>       </div>
>     </Wrapper>
>   )
> }
> ```
>
> {% endcode %}

### 21. Sort UI + Functionality continued - Products Page

{% hint style="info" %}
25\_ecommerce\_app/src/components/Filters.js

25\_ecommerce\_app/src/utils/helpers.js
{% endhint %}

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

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FVlkwrX2KKbt0WOryRD9I%2Fimage.png?alt=media&#x26;token=f7406603-65e3-4110-946f-29c1159ba420" alt=""><figcaption></figcaption></figure>

{% code title="components/Filter.js" %}

```javascript
  const categories = getUniqueValues(all_products, 'category')
  const companies = getUniqueValues(all_products, 'company')
  const colors = getUniqueValues(all_products, 'colors')
  console.log(categories)
  console.log(companies)
  console.log(colors)
```

{% endcode %}

{% code title="utils/helpers.js" %}

```javascript
export const formatPrice = (number) => {
  const formattedNumber = new Intl.NumberFormat('en-US', {
    style: 'currency',
    currency: 'USD',
  }).format(number / 100) // note that we still need to divide by 100 but don't have to format the decimals
  return formattedNumber // it automatically adds the currency representation (Adds $ at the beginning)
}

export const getUniqueValues = (data, type) => {
  let unique = data.map((item) => item[type])
  // type companies and categories are arrays. But color type is array of arrays.
  // So we need to flatten if they are colors
  if (type === 'colors') {
    unique = unique.flat()
  }

  return ['all', ...new Set(unique)]
}

```

{% endcode %}

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FyR3YAaH3JV3b3NENNocQ%2Fimage.png?alt=media&#x26;token=5dd406be-1d10-4dc2-bb4d-61d5489c1654" alt=""><figcaption></figcaption></figure>

#### Categories Filter

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

```javascript
 {/* categories */}
  <div className="form-control">
    <h5>category</h5>
    {categories.map((category, index) => {
      return <button key={index}>{category}</button>
    })}
  </div>
  {/* end of categories */}
```

This above code leads to this below UI

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FoRcPv6z6fFIjNT81Uvxu%2Fimage.png?alt=media&#x26;token=f2dcb087-8369-4c11-afa5-62989b1e857f" alt=""><figcaption></figcaption></figure>

Let's add active class and stuff&#x20;

```javascript
  <div className="form-control">
    <h5>category</h5>
    {categories.map((cat, index) => {
      return (
        // we can't add value here because of which updateFilters
        // will not get the value like input does
        <button
          key={index}
          onClick={updateFilters}
          name="category"
          type="button"
          className={cat.toLowerCase() === category ? 'active' : 'null'}
        >
          {cat}
        </button>
      )
    })}
  </div>
```

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

```javascript

  const updateFilters = (e) => {
    let name = e.target.name
    let value = e.target.value
    if (name === 'category') value = e.target.textContent // this one here
    dispatch({
      type: UPDATE_FILTERS,
      payload: { name: name, value: value },
    })
  }
```

#### Companies Filter

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

```javascript
 {/* companies filters */}
  <div className="form-control">
    <h5>company</h5>
    <select
      name="company"
      value={company}
      className="company"
      onChange={updateFilters}
    >
      {companies.map((comp, index) => {
        return (
          <option value={comp} key={index}>
            {`${comp[0].toUpperCase()}${comp.slice(1)}`}
          </option>
        )
      })}
    </select>
  </div>
  {/* end of companies filters */}
```

#### Colors Filter

```javascript
  {/* colors */}
  <div className="form-control">
    <h5>colors</h5>
    <div className="colors">
      {colors.map((clr, index) => {
        return (
          <button
            key={index}
            name="color"
            style={{ background: clr }}
            className={`color-btn ${clr === color && 'active'}`}
          ></button>
        )
      })}
    </div>
  </div>
  {/* end of colors */}
```

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.

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2F8J6jD8CuxKJdfJjQOsZo%2Fimage.png?alt=media&#x26;token=10402f55-0e3e-4058-9f82-69495ccdd2c3" alt=""><figcaption></figcaption></figure>

{% hint style="success" %}
So the take away in this section is

* Use e.target.value in input
* Use e.target.textContext in button
* Use data-set on any other component where above two are not possible.&#x20;
  * We need to name it as data- and then anything. data-color OR data-anything
    {% endhint %}

&#x20;So the color functionality looks this way

{% code title="Components/Filter.js" %}

```javascript
  {/* colors */}
  <div className="form-control">
    <h5>colors</h5>
    <div className="colors">
      {colors.map((clr, index) => {
        if (clr === 'all') {
          return (
            <button
              key={index}
              name="color"
              onClick={updateFilters}
              data-color="all"
              className={clr === color ? 'all-btn active' : 'all-btn'}
            >
              All
            </button>
          )
        }
        return (
          <button
            key={index}
            name="color"
            style={{ background: clr }}
            className={`color-btn ${clr === color && 'active'}`}
            data-color={clr}
            onClick={updateFilters}
          >
            {color === clr ? <FaCheck /> : null}
          </button>
        )
      })}
    </div>
  </div>
  {/* end of colors */}
```

{% endcode %}

{% code title="context/filter\_context.js" %}

```javascript

  const updateFilters = (e) => {
    let name = e.target.name
    let value = e.target.value
    if (name === 'category') value = e.target.textContent
    if (name === 'color') value = e.target.dataset.color
    dispatch({
      type: UPDATE_FILTERS,
      payload: { name: name, value: value },
    })
  }
```

{% endcode %}

#### Price Filter

Let's now work on the Price filter

```javascript
{/* price  */}
<div className="form-control">
  <h5>price</h5>
  <p className="price">{formatPrice(price)}</p>
  {/* this number range will always give us back the string the 
  moment we modify the value, hence 
  we need to convert this to number in updateFilter for this price */}
  <input
    type="range"
    name="price"
    onChange={updateFilters}
    min={min_price}
    max={max_price}
    value={price}
  />
</div>
{/* end of price  */}
```

```javascript
  const updateFilters = (e) => {
    let name = e.target.name
    let value = e.target.value
    if (name === 'category') value = e.target.textContent
    if (name === 'color') value = e.target.dataset.color
    if (name === 'price') value = +value // converting from str to num
    dispatch({
      type: UPDATE_FILTERS,
      payload: { name: name, value: value },
    })
  }
```

#### &#x20;Shipping filter

```javascript
{/* shipping */}
<div className="form-control shipping">
  <label htmlFor="shipping">free shipping</label>
  {/* id in the input should match the htmlFor of the label  */}
  <input
    type="checkbox"
    name="shipping"
    id="shipping"
    onChange={updateFilters}
    // we don't look for value here, it would be 
    // checked instead of value in a checkbox input
    checked={shipping}
  />
</div>
{/* end of shipping */}
```

```javascript
  const updateFilters = (e) => {
    let name = e.target.name
    let value = e.target.value
    if (name === 'category') value = e.target.textContent
    if (name === 'color') value = e.target.dataset.color
    if (name === 'price') value = +value // converting from str to num
    if (name === 'shipping') value = e.target.checked
    dispatch({
      type: UPDATE_FILTERS,
      payload: { name: name, value: value },
    })
  }
```

#### Clear Filters button

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

{% code title="Components/Filter.js" %}

```javascript
{/* clear filters button */}
<button type="button" className="clear-btn" onClick={clearFilters}>
    clear filters
</button>
```

{% endcode %}

{% code title="context/filter\_context" %}

```javascript
  const clearFilters = () => {
    dispatch({ type: CLEAR_FILTERS })
  }
```

{% endcode %}

{% code title="reducer/filter\_reducer.js" %}

```javascript
  if (action.type === CLEAR_FILTERS) {
    return {
      ...state,
      filters: {
        ...state.filters,
        text: '',
        category: 'all',
        company: 'all',
        color: 'all',
        price: state.filters.max_price,
        shipping: false,
      },
    }
  }
```

{% endcode %}

<figure><img src="https://1944679227-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MVEiPUp08kYt33g51v7%2Fuploads%2FQe09yUcHYeQiN9xi8wlP%2Fimage.png?alt=media&#x26;token=1b9e7f30-c6b6-4350-ac38-b6bf2788faf8" alt=""><figcaption></figcaption></figure>
