Несогласованность данных

Я изучаю фронтенд-разработку и пишу простую корзину покупок в 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

Заранее спасибо!

С уважением, Дариус

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
1
0
51
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я бы проверил, чтобы убедиться, что это сначала попадает в ваше приложение, записав его в console.info. Это может быть для вас небольшой проверкой здравомыслия, но на данный момент я не верю, что это происходит.
Во-первых, я бы полностью избавился от функции getProductData и просто передал функцию, предоставляемую useState, setProductData.
После этого я бы обновил способ использования этой функции. Вместо того, чтобы вам сейчас приходилось обновлять его каждый раз, когда количества обновляются функциями onclick, я бы удалил его из всего и решил использовать useEffect.

useEffect(()=>{
 setProductData(productQuantities)
}, [productQuantities]);

Я хотел бы убедиться, что с этими изменениями все по-прежнему настроено правильно, а затем убедиться, что они переданы в ваш компонент приложения.
Дайте мне знать, если у вас возникнут какие-либо проблемы, и Redux не так уж и плох, если вы в него войдете, и вы все равно все еще учитесь, поэтому я бы рекомендовал использовать его вместо этого :)

Привет, спасибо за ваш ответ. Кажется, что количественные значения прекрасно передаются компонентам App и Cart, я проверял с помощью console.info на каждом уровне. Проблема, похоже, решена с помощью хука useEffect, как и ваше предложение. Вместо того, чтобы передавать данные каждый раз, когда состояние изменяется во всех трех функциях увеличенияQuantity, уменьшенияQuantity и setSpecificQuantityValue, я просто использовал useEffect [useEffect(() => { passProductData(productQuantities); }, [productQuantities]);], чтобы передавать количества каждый раз, когда их состояние меняется. Кажется, это решило проблему.

Darius Molotokas 10.08.2024 09:02
Ответ принят как подходящий

Проблемы

В вашем коде есть несколько антишаблонов и различные проблемы.

  1. Состояние productData из App фактически дублируется в Productcard, и существуют проблемы с синхронностью состояний. Вот почему вы видите несовпадающие состояния в журнале консоли.
  2. 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<строка, число>>> Что это за тип? Я так понимаю это тип принятия состояния? Но как придумать что-то подобное? Могу ли я прочитать некоторые документы, чтобы лучше это понять?

Darius Molotokas 11.08.2024 07:13

@DariusMolotokas (1) Да, по сути, это объект со строковыми ключами (конечно!!!) и числовыми значениями. Я считаю, что что-то вроде { [key: string]: number } эквивалентно. (2) React.Dispatch<React.SetStateAction<T>> — это тип функции обновления состояния React, где T — это тип сохраняемого состояния. Я обычно копирую/вставляю его из IDE при наведении курсора на установщик. К сожалению, мне неизвестна какая-либо документация Typescript, специфичная для React, которая явно описывает это. В основном я научился этому, работая с проектами TS React.

Drew Reese 11.08.2024 07:52

Другие вопросы по теме