I am working on implementing shopping cart functionality in a React application, and while I have made some progress, I encountered challenges in managing the application’s state effectively. The application consists of two main components: BookList and Navbar. The BookList component displays a list of books, each with an “Add to Cart” button, while the Navbar component contains a cart icon that shows the total number of items in the cart.
Currently, I store the cart items in localStorage. My goal is to ensure that items are added to the cart seamlessly, without the need to refresh the page, and that the cart count in the Navbar is updated dynamically as items are added.
BookList.jsx:
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
import {
Card, CardBody, Image, Text, Stack, Heading, Input, Center, InputGroup, InputLeftElement, Box, IconButton
} from '@chakra-ui/react';
import { SearchIcon } from '@chakra-ui/icons';
import { BsCartPlusFill } from "react-icons/bs";
export const BookList = () => {
const navigate = useNavigate();
const [data, setData] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
const [cartData, setCartData] = useState(() => {
const savedCart = localStorage.getItem('cartData');
return savedCart ? JSON.parse(savedCart) : [];
});
useEffect(() => {
const fetchBooks = async () => {
try {
const response = await axios.get('http://localhost:5000/api/books');
setData(response.data.data);
} catch (err) {
console.log(err);
}
};
fetchBooks();
}, []);
const filteredBooks = data.filter(book =>
book.title.toLowerCase().includes(searchTerm.toLowerCase())
);
const handleAddToCart = (book) => {
const existingCartItem = cartData.find(cartItem => cartItem.id === book._id);
if (existingCartItem) {
const updatedCartData = cartData.map(cartItem =>
cartItem.id === book._id ? { ...cartItem, quantity: cartItem.quantity + 1 } : cartItem
);
setCartData(updatedCartData);
localStorage.setItem('cartData', JSON.stringify(updatedCartData));
window.location.reload();
} else {
const newCartItem = {
id: book._id,
title: book.title,
image: book.cover,
quantity: 1,
};
const updatedCartData = [...cartData, newCartItem];
setCartData(updatedCartData);
localStorage.setItem('cartData', JSON.stringify(updatedCartData));
window.location.reload();
}
};
return (
<div>
<Center>
<Text fontSize={'3xl'} fontWeight={'bold'} marginTop={'50px'}>
What Are you looking For ..?
</Text>
</Center>
<Center>
<InputGroup width="30%" margin={'30px'}>
<InputLeftElement pointerEvents='none'>
<SearchIcon color='black' />
</InputLeftElement>
<Input
placeholder="Search by book name"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
mb="10px"
size='md'
/>
</InputGroup>
</Center>
<div style={{ display: 'flex', flexDirection: 'row', flexWrap: 'wrap', justifyContent: 'space-around', marginTop: '20px' }}>
{filteredBooks.length > 0 ? (
filteredBooks.map((book) => (
<Box
position="relative"
key={book._id}
style={{ margin: '20px' }}
cursor="pointer"
role="group"
_hover={{
transform: 'scale(1.05)',
transition: 'all 0.4s ease-in-out',
boxShadow: 'xl',
}}
>
<Card
maxW="sm"
_groupHover={{
transform: 'scale(1.05)',
transition: 'all 0.4s ease-in-out',
boxShadow: 'xl',
}}
onClick={() => {
navigate(`/book/bookdetails/${book._id}`);
}}
>
<CardBody>
<Image
src={book.cover}
alt="Book cover"
borderRadius="xl"
boxSize="500px"
/>
<Stack mt="6" spacing="3">
<Heading size="md">
{book.title} <span style={{ color: 'green', fontSize: '15px' }}>${book.price}</span>
</Heading>
<Text>{book.author}</Text>
</Stack>
</CardBody>
</Card>
<IconButton
aria-label="Add to cart"
icon={<BsCartPlusFill />}
position="absolute"
bottom="5%"
right="5%"
colorScheme="green"
borderRadius="full"
size="lg"
_groupHover={{
transform: 'scale(1.4)',
transition: 'all 0.4s ease-in-out',
bottom: '3%',
right: '3%',
}}
onClick={(e) => {
e.stopPropagation();
handleAddToCart(book);
}}
/>
</Box>
))
) : (
<p>No books available :(</p>
)}
</div>
</div>
);
};
Navbar.jsx:
import React, { useState } from 'react';
import { Flex, Box, Heading, UnorderedList, ListItem, Link, Button, IconButton, Text, Image, Badge } from '@chakra-ui/react';
import { Link as RouterLink } from 'react-router-dom';
import { useNavigate } from 'react-router-dom';
import { FaCartShopping } from "react-icons/fa6";
import { MdDelete } from "react-icons/md";
import {
Popover,
PopoverTrigger,
PopoverContent,
PopoverBody,
} from '@chakra-ui/react';
export const Navbar = () => {
const navigate = useNavigate();
const isUserSignIn = !!localStorage.getItem('token');
const getCartDataArr = JSON.parse(localStorage.getItem('cartData'));
const [cartItemCount, SetCartItemCount] = useState(getCartDataArr ? getCartDataArr.length : 0);
const handleSignOut = () => {
localStorage.removeItem('token');
navigate('/login');
};
const handleRemoveFromCart = (itemId) => {
const cartData = JSON.parse(localStorage.getItem('cartData'));
const itemIndex = cartData.findIndex(cartItem => cartItem.id === itemId);
if (itemIndex > -1) {
if (cartData[itemIndex].quantity > 1) {
cartData[itemIndex].quantity -= 1;
} else {
cartData.splice(itemIndex, 1);
}
localStorage.setItem('cartData', JSON.stringify(cartData));
SetCartItemCount(cartData.length);
window.location.reload();
}
};
return (
<Box bg='#ffffff' color='white'>
<Flex
justify='space-between'
align='center'
px={8}
py={4}
height='80px'
maxW='1200px'
mx='auto'
>
<Box>
<Heading as={RouterLink} to="/" fontSize='2xl' color={'#2D3748'} _hover={{ color: '#005bc8' }}>
Logo
</Heading>
</Box>
<UnorderedList display='flex' listStyleType='none' m={0} gap='20px' alignItems='center'>
{isUserSignIn ? (
<>
<ListItem display="flex" alignItems="center">
<Popover>
<PopoverTrigger>
<Box position="relative">
<Button variant={'ghost'} p={0}>
<FaCartShopping size="20px" />
</Button>
<Badge
position="absolute"
top="-8px"
right="-8px"
bg="red.500"
borderRadius="full"
px={2}
fontSize="0.8em"
color="white"
>
{cartItemCount}
</Badge>
</Box>
</PopoverTrigger>
<PopoverContent>
<PopoverBody>
{getCartDataArr && getCartDataArr.length > 0 ? (
getCartDataArr.map((item) => (
<Box key={item.id} marginTop={'10%'}>
<Flex alignItems="center" justifyContent="space-between" mb={2} backgroundColor={'#f6f6f6'} padding={'10px'}>
<Image
src={item.image} // Ensure this matches your object property
alt="Cart Item Image"
boxSize="50px"
objectFit="contain"
maxH="50px"
maxW="50px"
borderRadius="md"
/>
<Text color={'black'} ml={2}>{item.title} x{item.quantity}</Text>
<IconButton aria-label="Delete item" icon={<MdDelete color='red' />} onClick={() => handleRemoveFromCart(item.id)} variant="ghost" />
</Flex>
</Box>
))
) : (
<Text>No items in the cart.</Text>
)}
<Button backgroundColor={'#85ff8d'} _hover={{ backgroundColor: "#41ff4e" }} width="100%" mt={4}>
Proceeding
</Button>
</PopoverBody>
</PopoverContent>
</Popover>
</ListItem>
<ListItem>
<Link as={RouterLink} to="/account" color={'#2D3748'} _hover={{ backgroundColor: "#000", color: '#fff' }}>
Account
</Link>
</ListItem>
<ListItem>
<Link as={RouterLink} to="/login" color={'#2D3748'} _hover={{ backgroundColor: "#000", color: '#fff' }} onClick={handleSignOut}>
Signout
</Link>
</ListItem>
</>
) : (
<>
<ListItem display="flex" alignItems="center">
<Popover>
<PopoverTrigger>
<Box position="relative">
<Button variant={'ghost'} p={0}>
<FaCartShopping size="20px" />
</Button>
<Badge
position="absolute"
top="-8px"
right="-8px"
bg="red.500"
borderRadius="full"
px={2}
fontSize="0.8em"
color="white"
>
{cartItemCount}
</Badge>
</Box>
</PopoverTrigger>
<PopoverContent>
<PopoverBody>
{getCartDataArr && getCartDataArr.length > 0 ? (
getCartDataArr.map((item) => (
<Box key={item.id} marginTop={'10%'}>
<Flex alignItems="center" justifyContent="space-between" mb={2} backgroundColor={'#f6f6f6'} padding={'10px'}>
<Image
src={item.image} // Ensure this matches your object property
alt="Cart Item Image"
boxSize="50px"
objectFit="contain"
maxH="50px"
maxW="50px"
borderRadius="md"
/>
<Text color={'black'} ml={1}>{item.title} x{item.quantity} </Text>
<IconButton aria-label="Delete item" icon={<MdDelete color='red' />} onClick={() => handleRemoveFromCart(item.id)} variant="ghost" />
</Flex>
</Box>
))
) : (
<Text>No items in the cart.</Text>
)}
<Button backgroundColor={'#85ff8d'} _hover={{ backgroundColor: "#41ff4e" }} width="100%" mt={4}>
Proceeding
</Button>
</PopoverBody>
</PopoverContent>
</Popover>
</ListItem>
<ListItem>
<Link as={RouterLink} to="/login" color={'#2D3748'} _hover={{ backgroundColor: "#000", color: '#fff' }}>
Login
</Link>
</ListItem>
<ListItem>
<Link as={RouterLink} to="/signup" color={'#2D3748'} _hover={{ backgroundColor: "#000", color: '#fff' }}>
Signup
</Link>
</ListItem>
</>
)}
</UnorderedList>
</Flex>
</Box>
);
};
if you need from me to provide more informations just let me know.