我正在购物车上工作,但是当我试图将项目添加到购物车中时,我一直得到这个错误。
react_devtools_backend.js:4012警告:在渲染不同的组件(ShoppingCart
)时无法更新组件(App
)。要定位ShoppingCart
内部的坏setState()调用,请按照https://reactjs.org/link/setstate-in-render中描述的堆栈跟踪在ShoppingCart (http://localhost:3001/static/js/bundle.js:911:5)at App (http://localhost:3001/static/js/bundle.js:105:94)
我认为问题可能是在我的ProductCards组件与onClick功能,但我不知道。请帮助!
import React, { useEffect, useState } from 'react'
//Components
import FilterableTable from './components/FilterableTable'
import SearchBar from './components/SearchBar'
import Navbar from './components/Navbar'
import ShoppingCart from './components/ShoppingCart'
function App() {
//Snowboard Array
const SnowboardArray = [
{
id: 1,
name: "Thunder Bolt",
availability: "in stock",
shape: "directional",
Level: "Expert",
terrain: '',
size: "156",
price: "500",
rating: '3',
image: thunderBolt,
},
// Removed array to make code smaller
]
//State
const [filteredSearch, setFilteredSearch] = useState('');
//State for shopping cart
//Cart visibility
const [cartsVisibility, setCartsVisibility] = useState(false)
//Products in cart
const [productsInCart, setProductsInCart] = useState(JSON.parse(localStorage.getItem("shopping-cart")) || []);
// Use effect
useEffect(()=> {
localStorage.setItem("shopping-cart", JSON.stringify(productsInCart))
}, [productsInCart])
//Functions
//Add to cart
function addProductToCart(item) {
const newItem = {...item, count: 1} // creating a new object. Grabbing item and all of its properties with spread operator. Adding count prop.
setProductsInCart([...productsInCart, newItem ]); //Adding all elements currently in state plus the new item with its new count prop.
}
// Update quanitity
function onQuantityChange(itemId, count) { // Takes in product ID and count props from item that's being changed
setProductsInCart((currentState) => { // updating state with setter function and referring to current state (values currently in state)
const productIndex = currentState.findIndex((item) => item.id === itemId); // Grabbing current state and chaining on the findIndex method that iterates
// the array and looks for the index of the item in the current state with the ID that matches the ID from the item that's being queried.
// An IF check is ran to check if the state value 'productsInCart' is not equal to -1. If this is true, the code is executed.
if (productsInCart !== -1) { // Checks if there's items in the cart
currentState[productIndex].count = count; // searches the current state array for the index that matches. Accesses the count property and sets it to 'count
// *currentState[productIndex = integer]*
}
return [...currentState];
})
}
// Remove Item
function onProductRemove(item) { // When the button is pressed, the item will be passed to the function
setProductsInCart((currentState) => { // Using state setter function to update state. Referring to currentState to access the current state.
const productsIndex = currentState.findIndex((product) => product.id === item.id);
// Accessing current state and calling the find index method to search for the index of the product who's product ID matches the ID of the item being queried.
// ProductsIndex now holds the index of the item that matches the one being queried.
// If the item's index is valid (currently in the array), it is removed from the array using the splice method.
if (productsIndex !== -1) {
currentState.splice(productsIndex, 1);
}
// Otherwise, the original current state is returned
return [...currentState];
})
}
return (
<>
<Navbar setCartsVisibility={setCartsVisibility} productsInCart={productsInCart}/>
<SearchBar arr={SnowboardArray}/>
<FilterableTable arr={SnowboardArray} setFilteredSearch={setFilteredSearch} setProductsInCart={setProductsInCart} addProductToCart={addProductToCart}/>
<ShoppingCart cartsVisibility={cartsVisibility} productsInCart={productsInCart} onQuantityChange={onQuantityChange} onProductRemove={onProductRemove} onClose={() => setCartsVisibility(false)
}/>
</>
)
}
export default App
import React from 'react'
//Css
import '../css/ShoppingCart.scss'
//Icons
import DeleteIcon from '@mui/icons-material/Delete';
import CloseIcon from '@mui/icons-material/Close';
function ShoppingCart({
cartsVisibility, //Visibility of shopping cart
productsInCart, //Products in shopping cart
onProductRemove, //On products remove
onClose, //When shopping cart is closed
onQuantityChange, //Change quantity of product
}) {
return (
<div className="modal" style={{display: cartsVisibility ? 'block' : 'none'}}>
<div className="shoppingCart">
<div className="header">
<h2>Shopping Cart</h2>
{/* Close btn */}
<button className='btn close-btn' onClick={onClose}>
<CloseIcon />
</button>
</div>
{/* Shopping Cart products */}
<div className="cart-products">
{productsInCart.length === 0 && ( <span className='empty-text'>Your cart is currently empty.</span>)}
{productsInCart.map((product) => (
<div className='cart-product' key={product.id}>
<img src={product.image} alt={product.name} />
<div className="product-info">
<h3>{product.name}</h3>
<span className='product-price'>€{product.price * product.count}</span>
</div>
<select className='count' value={product.count} onChange={(event) => onQuantityChange(product.id, event.target.value)}>
{[...Array(10).keys()].map(number => {
const num = number + 1;
return <option value={{num}} key={num}>{num}</option>
// return <option value={num} key={num}>{num}</option>
})}
</select>
<button className='btn remove-btn' onClick={onProductRemove(product)}><DeleteIcon /></button>
</div>
))}
{productsInCart.length > 0 && <button className='btn checkout-btn'>Proceed to checkout.</button>}
</div>
</div>
</div>
)
}
export default ShoppingCart
import React, { useState, useEffect, useMemo } from 'react'
//Components
import TypeSelector from './TypeSelector'
import ProductCards from './ProductCards'
function FilterableTable({arr, setProductsInCart, addProductToCart}) {
// State to store the all items / full product array
const [allObjects, setAllobjects] = useState([]);
// State to store the filtered array 'type'
const [filteredObjects, setFilteredObjects] = useState();
// Use effect to put the full product array inside the all objects state variable on mount
useEffect(() => {
setAllobjects(arr);
}, []);
// Function to filter the main array with the selected filter value stored in the state
// Original function that wasn't working.
// function getFilteredList() {
// if (!filteredObjects) {
// return allObjects;
// }
// if (filteredObjects === 'directional' || 'directional-twin' || 'twin' ) {
// return allObjects.filter((object) => object.shape === filteredObjects)
// } else if (filteredObjects === '154' || '156' || '157' ) {
// return allObjects.filter((object) => object.size === filteredObjects)
// }
// }
function getFilteredList() {
if (!filteredObjects) {
return allObjects;
}
const allowedSizes = ['154', '156', '157'];
if (allowedSizes.includes(filteredObjects)) {
return allObjects.filter((object) => object.size === filteredObjects);
} else {
return allObjects.filter((object) => object.shape === filteredObjects);
}
}
// Use memo hook is used to prevent multiple calls. Only calls function when state changes
// Everything is now stored in FilteredList variable
let filteredList = useMemo(getFilteredList, [allObjects, filteredObjects]);
return (
<>
{/* Dropdown selector */}
<TypeSelector setFilteredObjects={setFilteredObjects}/>
{/* Product cards */}
<div className="card-container" style={{display: 'flex', justifyContent: 'center', width: '100%', flexWrap: 'wrap', gap: '1rem'}}>
{filteredList.map((object, index) => (
<ProductCards {...object} key={index} setProductsInCart={setProductsInCart} addProductToCart={addProductToCart} />
))}
</div>
</>
)
}
export default FilterableTable
import React from 'react'
//Styles
import '../css/ProductCards.scss';
//Components
import Ratings from '../components/Ratings';
function ProductCards ({addProductToCart, ...object}) {
return (
<div className="card">
<img src={object.image} alt='Image' />
<button onClick={() => addProductToCart(object)}>Add to cart</button>
<h3>{object.name}</h3>
<p>Directional freeride snowboard</p>
{/* <span>reviews</span> */}
<span>€{object.price}</span>
<Ratings arr={object} />
{/* <p>{object.id}</p> */}
<p>{object.types}</p>
</div>
)
}
export default ProductCards
问题是您直接向onClick回调提供onProductRemove
。这将导致在呈现组件时执行函数,因为onProductRemove
的返回值被设置为回调。
为了解决这个问题,你需要更新回调为:
<button className='btn remove-btn' onClick={ ()=> onProductRemove(product)}><DeleteIcon /></button>
通过在它之前添加一个箭头函数,我们可以防止onProductRemove
函数在click事件之前被调用。