我正在致力于在 React 应用程序中实现购物车功能,虽然我取得了一些进展,但在有效管理应用程序状态方面遇到了挑战。该应用程序由两个主要组件组成:BookList和Navbar。 BookList 组件显示书籍列表,每个书籍都有一个“添加到购物车”按钮,而 Navbar 组件包含一个购物车图标,显示购物车中的商品总数。
目前,我将购物车商品存储在 localStorage 中。我的目标是确保将商品无缝添加到购物车,而无需刷新页面,并且导航栏中的购物车计数会随着商品的添加而动态更新。
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>
);
};
如果您需要我提供更多信息,请告诉我。
使用 React,如果你需要调用
window.reload
,那你就错了。
每当从
useState
挂钩返回的值更新时,页面都会重新呈现,并且消耗该值的任何地方都会更新。
以这个简单的组件为例。
function Counter(){
const [count, setCount] = useState(0)
return <button onClick={()=> setCount(prev=> prev+1)}>Click Count: {count}</button>
}
每当您单击按钮更新状态时,React 都会重新渲染组件,并且按钮的
count
将在页面上更新。 所有国家都是如此。 所以如果你有这样的事情
const [cartData, setCartData] = useState([])
随时打电话
setCartData((pre => ([...pre, ....your new data....]))
您可以像这样在 JSX 中使用该状态,并且当您通过调用
setCartData
添加或删除值时,页面将会更新
<div>Cart Count {cartData.length}</div>