Я изучаю фронтенд-разработку и пишу простую корзину покупок в React, чтобы учиться. Когда я меняю количество продукта, я хочу создать объект значений количества по идентификатору продукта и передать его в компонент корзины для отображения позже. Данные о количествах, которые я собираю, кажутся правильными, потому что я вижу, что они правильно отображаются в состоянии QTY:, как показано на этом рисунке:
,но когда я передаю состояние количества родительскому элементу, а затем от родителя компоненту корзины покупок и console.info, данные кажутся повсюду... Иногда они вообще не подбираются, иногда слишком малы на 1 и так далее...
Я видел несколько видеороликов, в которых люди создают корзину для покупок и, кажется, используют инструменты управления состоянием, такие как реакция useContext или Redux, и я понимаю, что глобальное состояние было бы намного лучше, чем передача данных от дочернего элемента к родительскому, а затем от родителя к другому дочернему элементу, но Я думаю, что я еще не готов к инструментам управления состоянием.
Компонент приложения:
import { useState } from "react";
import ProductCard from "./components/ProductCard";
import ShoppingCart from "./components/ShoppingCart";
function App() {
let [productData, setProductData] = useState({});
function getProductData(cartData: object) {
setProductData(cartData);
}
return (
<>
<div>
<ProductCard passProductData = {getProductData} />
<ShoppingCart productData = {productData} />
</div>
</>
);
}
export default App;
Компонент корзины:
interface Props {
productData: object;
}
function ShoppingCart({ productData }: Props) {
return (
<>
<button
onClick = {() => {
console.info(JSON.stringify(productData));
}}
className = "bg-slate-400"
>
Test passed data
</button>
<h1 className = "font-bold">Your cart</h1>
<p className = "font-bold">Order Total</p>
</>
);
}
export default ShoppingCart;
Компонент карточки товара:
import productList from "../data/productList";
import { useState } from "react";
interface Props {
passProductData: (quantities: object) => void;
}
function ProductCard({ passProductData }: Props) {
let [newProductList, changeProductList] = useState(productList);
let [productQuantities, setProductQuantities] = useState({
0: 0,
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
});
function revealQuantityInput(id: number) {
changeProductList(
newProductList.map((product) => {
if (product.id === id) {
return { ...product, quantityVisibility: true };
} else return product;
})
);
}
function increaseQuantity(id: number) {
for (let [key, value] of Object.entries(productQuantities)) {
if (Number(key) === id) {
setProductQuantities({
...productQuantities,
[key]: value + 1,
});
}
}
passProductData(productQuantities);
}
function decreaseQuantity(id: number) {
for (let [key, value] of Object.entries(productQuantities)) {
if (Number(key) === id) {
setProductQuantities({
...productQuantities,
[key]: value > 0 ? value - 1 : value,
});
}
}
passProductData(productQuantities);
}
function setSpecificQuantityValue(id: number, quantity: number) {
newProductList.map((product) => {
if (product.id === id) {
return setProductQuantities({
...productQuantities,
[product.id]: quantity,
});
}
});
passProductData(productQuantities);
}
return (
<>
{newProductList.map((product) => (
<div className = "p-6 mb-6 [&>*]:mb-4" key = {product.id}>
<img src = {product.image} />
<div
className = "bg-green-600 p-2"
onClick = {() => revealQuantityInput(product.id)}
>
{product.quantityVisibility ? (
<div>
<p onClick = {() => decreaseQuantity(product.id)}>-</p>
<input
type = "number"
id = {"quantity" + product.id}
min = "1"
max = "9"
step = "1"
onChange = {(e) =>
setSpecificQuantityValue(product.id, Number(e.target.value))
}
className = "bg-gray-300 w-[150px] opacity-100 mr-3"
/>
<p onClick = {() => increaseQuantity(product.id)}>+</p>
</div>
) : (
<p>Add to Cart</p>
)}
</div>
<p>{product.category}</p>
<p>{product.name}</p>
<p>{product.price}</p>
<p>QTY: {JSON.stringify(productQuantities)}</p>
</div>
))}
</>
);
}
export default ProductCard;
Данные о продукте:
import productTypes from "../types/productTypes"
const productList:productTypes[] = [
{
id: 0,
image: "../../images/image-waffle-mobile.jpg",
name: "Waffle with Berries",
category: "Waffle",
price: 6.50,
quantityVisibility: false,
},
{
id: 1,
image: "../../images/image-creme-brulee-mobile.jpg",
name: "Vanilla Bean Crème Brûlée",
category: "Crème Brûlée",
price: 7.00,
quantityVisibility: false,
},
{
id: 2,
image: "../../images/image-macaron-mobile.jpg",
name: "Macaron Mix of Five",
category: "Macaron",
price: 8.00,
quantityVisibility: false,
},
{
id: 3,
image: "../../images/image-tiramisu-mobile.jpg",
name: "Classic Tiramisu",
category: "Tiramisu",
price: 5.50,
quantityVisibility: false,
},
{
id: 4,
image: "../../images/image-baklava-mobile.jpg",
name: "Pistachio Baklava",
category: "Baklava",
price: 4.00,
quantityVisibility: false,
},
{
id: 5,
image: "../../images/image-meringue-mobile.jpg",
name: "Pistachio Baklava",
category: "Baklava",
price: 4.00,
quantityVisibility: false,
},
]
export default productList
Заранее спасибо!
С уважением, Дариус
Я бы проверил, чтобы убедиться, что это сначала попадает в ваше приложение, записав его в console.info. Это может быть для вас небольшой проверкой здравомыслия, но на данный момент я не верю, что это происходит.
Во-первых, я бы полностью избавился от функции getProductData и просто передал функцию, предоставляемую useState, setProductData.
После этого я бы обновил способ использования этой функции. Вместо того, чтобы вам сейчас приходилось обновлять его каждый раз, когда количества обновляются функциями onclick, я бы удалил его из всего и решил использовать useEffect.
useEffect(()=>{
setProductData(productQuantities)
}, [productQuantities]);
Я хотел бы убедиться, что с этими изменениями все по-прежнему настроено правильно, а затем убедиться, что они переданы в ваш компонент приложения.
Дайте мне знать, если у вас возникнут какие-либо проблемы, и Redux не так уж и плох, если вы в него войдете, и вы все равно все еще учитесь, поэтому я бы рекомендовал использовать его вместо этого :)
В вашем коде есть несколько антишаблонов и различные проблемы.
productData
из App
фактически дублируется в Productcard
, и существуют проблемы с синхронностью состояний. Вот почему вы видите несовпадающие состояния в журнале консоли.ProductCard
ставит обновления состояния в очередь родительского компонента сразу после постановки в очередь обновления состояния productQuantities
. Обновления состояния React не обрабатываются немедленно, поэтому старое состояние передается родителю. Это также приводит к количественным расхождениям в журналах консоли.Состояние productData
в App
должно быть единственным источником истины.
Обновите App
, чтобы передать productData
и setProductData
компоненту ProductCard
. Используйте функцию ленивого инициализатора состояния, чтобы инициализировать состояние с начальными нулевыми количествами по идентификатору продукта.
import { useState } from "react";
import ProductCard from "./components/ProductCard";
import ShoppingCart from "./components/ShoppingCart";
import productList from "./data/productList";
export default function App() {
const [productData, setProductData] = useState<Record<string, number>>(() =>
productList.reduce(
(quantities, { id }) => ({
...quantities,
[id]: 0,
}),
{}
)
);
return (
<div>
<ProductCard setProductData = {setProductData} productData = {productData} />
<ShoppingCart productData = {productData} />
</div>
);
Обновите компонент ProductCard
, чтобы ставить обновления состояния непосредственно в родительское состояние. Используйте обновления функционального состояния, чтобы правильно обновить предыдущее значение состояния.
import productListData from "../data/productList";
import { useState } from "react";
interface Props {
setProductData: React.Dispatch<React.SetStateAction<Record<string, number>>>;
productData: Record<string, number>;
}
function ProductCard({ setProductData, productData }: Props) {
const [productList, changeProductList] = useState(productListData);
function revealQuantityInput(id: number) {
changeProductList((productList) =>
productList.map((product) =>
product.id === id
? { ...product, quantityVisibility: true }
: product
)
);
}
function increaseQuantity(id: number) {
setProductData((quantities) => ({
...quantities,
[id]: quantities[id] + 1,
}));
}
function decreaseQuantity(id: number) {
setProductData((quantities) => ({
...quantities,
[id]: quantities[id] - 1,
}));
}
function setSpecificQuantityValue(id: number, quantity: number) {
setProductData((quantities) => ({
...quantities,
[id]: quantity,
}));
}
return productList.map((product) => (
<div className = "p-6 mb-6 [&>*]:mb-4" key = {product.id}>
<img src = {product.image} />
<div
className = "bg-green-600 p-2"
onClick = {() => revealQuantityInput(product.id)}
>
{product.quantityVisibility ? (
<div>
<p onClick = {() => decreaseQuantity(product.id)}>-</p>
<input
type = "number"
id = {"quantity" + product.id}
min = "1"
max = "9"
step = "1"
onChange = {(e) =>
setSpecificQuantityValue(product.id, Number(e.target.value))
}
className = "bg-gray-300 w-[150px] opacity-100 mr-3"
/>
<p onClick = {() => increaseQuantity(product.id)}>+</p>
</div>
) : (
<p>Add to Cart</p>
)}
</div>
<p>{product.category}</p>
<p>{product.name}</p>
<p>{product.price}</p>
<p>QTY: {JSON.stringify(productData)}</p>
</div>
));
}
export default ProductCard;
Спасибо за ваш ответ! У меня есть несколько вопросов относительно типов. 1. Тип <Record<string, Number>> — это просто объект {prop1: string, prop2: Number}? 2. React.Dispatch<React.SetStateAction<Record<строка, число>>> Что это за тип? Я так понимаю это тип принятия состояния? Но как придумать что-то подобное? Могу ли я прочитать некоторые документы, чтобы лучше это понять?
@DariusMolotokas (1) Да, по сути, это объект со строковыми ключами (конечно!!!) и числовыми значениями. Я считаю, что что-то вроде { [key: string]: number }
эквивалентно. (2) React.Dispatch<React.SetStateAction<T>>
— это тип функции обновления состояния React, где T
— это тип сохраняемого состояния. Я обычно копирую/вставляю его из IDE при наведении курсора на установщик. К сожалению, мне неизвестна какая-либо документация Typescript, специфичная для React, которая явно описывает это. В основном я научился этому, работая с проектами TS React.
Привет, спасибо за ваш ответ. Кажется, что количественные значения прекрасно передаются компонентам App и Cart, я проверял с помощью console.info на каждом уровне. Проблема, похоже, решена с помощью хука useEffect, как и ваше предложение. Вместо того, чтобы передавать данные каждый раз, когда состояние изменяется во всех трех функциях увеличенияQuantity, уменьшенияQuantity и setSpecificQuantityValue, я просто использовал useEffect [useEffect(() => { passProductData(productQuantities); }, [productQuantities]);], чтобы передавать количества каждый раз, когда их состояние меняется. Кажется, это решило проблему.