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

in hive-133716 •  4 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

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!