E-COMMERCE - Create a shopping cart with React.js

in Steem Sri Lanka4 years ago

react.png

Today we are going to create a simple shopping cart with React.js we are going to consume the API of Fakestoreapi.com we will also use material-ui that will provide us with Style Components to shape our Web Application, we will also learn to use react-query to get the API data



We will create a folder on the desktop named shopping-cart /

Once the folder is created, we navigate to it and execute


npx create-react-app .


We will install the following modules:

npm i react-query @material/core @material/icons


index.js

import React from "react";
import ReactDOM from "react-dom";
import { QueryClient, QueryClientProvider } from "react-query";
import App from "./App";
const client = new QueryClient();
ReactDOM.render(
  <QueryClientProvider client={client}>
    <App />
  </QueryClientProvider>,
  document.getElementById("root")
);


App.js

import { useState } from "react";
import { useQuery } from "react-query";
//Components
import Item from "./components/Item";
import CartList from "./components/CartList";
import Navbar from "./components/Navbar";
import Drawer from "@material-ui/core/Drawer";
import LinearProgress from "@material-ui/core/LinearProgress";
import Grid from "@material-ui/core/Grid";
import "./custom.css";
const getProducts = async () =>
  await (await fetch("https://fakestoreapi.com/products/")).json();
const App = () => {
  const { isLoading, error, data } = useQuery("products", getProducts);
  const [cartOpen, setCartOpen] = useState(false);
  const [cartItems, setCartItems] = useState([]);
  const getTotalItems = (cartItems) =>
    cartItems.reduce((acum, i) => acum + i.amount, 0);
  const handleAddItemToCart = (item) => {
    setCartItems((prev) => {
      // Search the item in the array
      const isItemInTheCart = prev.find((i) => i.id === item.id);
      if (isItemInTheCart) {
        return prev.map((i) =>
          i.id === item.id ? { ...i, amount: i.amount + 1 } : i
        );
      }
      return [...prev, { ...item, amount: 1 }];
    });
  };
  const handleRemoveItemFromCart = (id) => {
    setCartItems((prev) => {
      const foundItem = prev.find((i) => i.id === id);
      if (foundItem) {
        if (foundItem.amount === 1) {
          const newArray = prev.filter((i) => i.id !== id);
          return newArray;
        } else {
          return prev.map((i) =>
            i.id === id ? { ...i, amount: i.amount - 1 } : i
          );
        }
      } else {
        return prev;
      }
    });
  };
  if (isLoading) return <LinearProgress>;
  if (error) return error.message;
  return (
    <>
      <Navbar
        getTotalItems={getTotalItems(cartItems)}
        setCartOpen={setCartOpen}
      ></Navbar>
      <div className="main">
        <Drawer
          anchor="right"
          open={cartOpen}
          onClose={() => setCartOpen(false)}
        >
          <CartList
            cartItems={cartItems}
            handleAddItemToCart={handleAddItemToCart}
            handleRemoveItemFromCart={handleRemoveItemFromCart}
          />
        </Drawer>
        <Grid container spacing={3}>
          {data?.map((item) => (
            <Grid key={item.id} item xs={12} sm={4}>
              <Item item={item} handleAddItemToCart={handleAddItemToCart} />
            </Grid>
          ))}
        </Grid>
      </div>
    </>
  );
};
export default App;


We create a custom css file

custom.css

@import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300&display=swap');
body {
    margin :0;
    padding:0;
    font-family: 'Quicksand', sans-serif;
}
.main {
    padding:40px
}
.cartIcon {
    position: fixed;
    top: 10px;
    right: 140px;
    cursor:pointer;
    z-index: 100;
    background: yellow;
}
aside {
    width:460px;
    margin : 20px;
}
.itemCart {
    display:flex;
    justify-content: space-between;
}
.itemCart img{
    max-width: 80px;
    object-fit: cover;
    margin-left: 40px;
}
.itemCart div{
    flex : 1
}
.itemInfo {
    display: flex;
    justify-content : space-between
}
.buttons {
    display:flex;
    justify-content : space-between
}


Components

components/Navbar.js

import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import AppBar from "@material-ui/core/AppBar";
import Toolbar from "@material-ui/core/Toolbar";
import IconButton from "@material-ui/core/IconButton";
import Badge from "@material-ui/core/Badge";
import Menu from "@material-ui/core/Menu";
import AddShoppingCart from "@material-ui/icons/AddShoppingCart";
const useStyles = makeStyles(() => ({
  grow: {
    flexGrow: 1,
  }
}));
export default function Navbar({ getTotalItems, setCartOpen }) {
  const classes = useStyles();
  const [anchorEl, setAnchorEl] = React.useState(null);
  const [mobileMoreAnchorEl, setMobileMoreAnchorEl] = React.useState(null);
  const isMenuOpen = Boolean(anchorEl);
  const isMobileMenuOpen = Boolean(mobileMoreAnchorEl);
  const handleMobileMenuClose = () => {
    setMobileMoreAnchorEl(null);
  };
  const handleMenuClose = () => {
    setAnchorEl(null);
    handleMobileMenuClose();
  };
  const menuId = "primary-search-account-menu";
  const renderMenu = (
    <Menu
      anchorEl={anchorEl}
      anchorOrigin={{ vertical: "top", horizontal: "right" }}
      id={menuId}
      keepMounted
      transformOrigin={{ vertical: "top", horizontal: "right" }}
      open={isMenuOpen}
      onClose={handleMenuClose}
    ></Menu>
  );
  const mobileMenuId = "primary-search-account-menu-mobile";
  const renderMobileMenu = (
    <Menu
      anchorEl={mobileMoreAnchorEl}
      anchorOrigin={{ vertical: "top", horizontal: "right" }}
      id={mobileMenuId}
      keepMounted
      transformOrigin={{ vertical: "top", horizontal: "right" }}
      open={isMobileMenuOpen}
      onClose={handleMobileMenuClose}
    ></Menu>
  );
  return (
    <div className={classes.grow}>
      <AppBar position="static">
        <Toolbar>
          <b>Shopping Cart</b>   |   
          <div className={classes.grow} />
          <div onClick={() => setCartOpen(true)}
          >
            <IconButton aria-label="show 4 new mails" color="inherit">
              <Badge badgeContent={getTotalItems} color="secondary">
                <AddShoppingCart />
              </Badge>
            </IconButton>
          </div>
        </Toolbar>
      </AppBar>
      {renderMobileMenu}
      {renderMenu}
    </div>
  );
}


components/Item.js

import Card from "@material-ui/core/Card";
import { makeStyles } from '@material-ui/core/styles';
import CardActionArea from "@material-ui/core/CardActionArea";
import CardActions from "@material-ui/core/CardActions";
import CardContent from "@material-ui/core/CardContent";
import CardMedia from "@material-ui/core/CardMedia";
import Button from "@material-ui/core/Button";
const useStyles = makeStyles({
    root: {
      maxWidth: 345,
    },
  });
export default function Item({item, handleAddItemToCart }) {
    const classes = useStyles();
  return (
    <Card className={classes.root}>
      <CardActionArea>
        <CardMedia
          component="img"
          alt={item.title}
          height="200"
          image={item.image}
          title={item.title}
        />
        <CardContent>
          {item.description}
        </CardContent>
      </CardActionArea>
      <CardActions>
      <Button size="small" color="secondary">
          $ {item.price}
        </Button>
        <Button size="small" color="primary" onClick = { () => handleAddItemToCart(item)}>
          Add to Cart
        </Button>
      </CardActions>
    </Card>
  );
}


components/CartList.js

import CartItem from "./CartItem";
const CartList = ({ cartItems, handleAddItemToCart, handleRemoveItemFromCart  }) => {
  const calculeTotal = cartItems.reduce(
    (sum, i) => sum + i.amount * i.price,
    0
  );
  return (
    <aside>
      <h1>Cart</h1>
      {cartItems.length === 0 ? <h3>No products yet...</h3> : null}
      <div>
        {cartItems.map((i) => (
          <CartItem item={i} handleAddItemToCart={handleAddItemToCart} handleRemoveItemFromCart={handleRemoveItemFromCart} />
        ))}
      </div>
      <h2>Total: {calculeTotal.toFixed(2)}</h2>
    </aside>
  );
};
export default CartList;


components/CartItem.js

import Button from "@material-ui/core/Button";
const CartItem = ({ item, handleAddItemToCart, handleRemoveItemFromCart }) => {
  return (
    <aside>
      <div className="itemCart">
        <div>
          <h3>{item.title}</h3>
          <div className="itemInfo">
            <p>Precio: ${item.price}</p>
            <p>Total: ${(item.amount * item.price).toFixed(2)}</p>
          </div>
          <div className="buttons">
            <Button onClick={ () => handleRemoveItemFromCart(item.id)} size="small" disableElevation variant="contained">
              -
            </Button>
            <p> {item.amount}
            <Button
              size="small"
              disableElevation
              variant="contained"
              onClick={() => handleAddItemToCart(item)}
            >
              +
            </Button>
          </div>
        </div>
        <img src={item.image} />
      </div>
    </aside>
  );
};
export default CartItem;



And with those friends we reached the end of the tutorial, I hope you enjoyed it and until next time!


Visit my official website for budges and much more

TupaginaOnline.net