如何在reactjs中将商品添加到购物车而不刷新

问题描述 投票:0回答:1

我正在致力于在 React 应用程序中实现购物车功能,虽然我取得了一些进展,但在有效管理应用程序状态方面遇到了挑战。该应用程序由两个主要组件组成:BookListNavbarBookList 组件显示书籍列表,每个书籍都有一个“添加到购物车”按钮,而 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>
  );
};

如果您需要我提供更多信息,请告诉我。

javascript reactjs state shopping-cart react-fullstack
1个回答
0
投票

使用 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>
最新问题
© www.soinside.com 2019 - 2025. All rights reserved.