Нажмите уникальный объект в React.js - PullRequest
0 голосов
/ 02 июня 2018

Я создаю ужасно некорректное приложение электронной коммерции, используя React, просто для удовольствия и пытаюсь выяснить, как установить состояние для определенного объекта после его помещения в массив.

У меня естьмассив cart, в который я добавляю элементы, которые я добавил из моего начального массива items, в котором хранятся все мои продукты.

Неудобная вещь заключается в том, что у меня есть товар на складе.Допустим, у моего продукта chocolate есть 5 на складе, и каждый раз, когда я нажимаю на этот объект chocolate, он накапливается и добавляет в корзину тот же элемент, что и:

enter image description here

Я хочу отправить объект chocolates в массив корзины, но не хочу отображать дубликат, если он уже существует.Вместо этого я хочу достичь чего-то, где добавляется объект chocolate, но его количество изменяется соответственно при каждом добавлении.Это будет выглядеть примерно так:

enter image description here

Как мне добиться чего-то подобного?Может быть, проверить, не добавлен ли этот объект в массив cart, и если вместо этого он отображает дубликат, просто нажмите значения и обновите количество этого элемента?

Застряли на несколько часови был бы очень признателен за некоторые советы.

class App extends Component {
  state = {
    cart: [],
    items: [
      { id: uuid(), name: 'chocolate', price: 10, remaining: 5 },
      { id: uuid(), name: 'strawberry', price: 50, remaining: 10 },
      { id: uuid(), name: 'banana', price: 43, remaining: 20 }
    ],
    total: 0,
    addToCartMessage: false,
    removeFromCartMessage: false,
    searchTerm: ''
  }

  addItem = item => {
    const { cart, items, total } = this.state

    cart.push({ id: item.id, name: item.name, price: item.price })

    const remaining = item.remaining--    

    const totalPrice = cart.reduce((a, b) => a + b.price, 0)

    this.setState({
      total: totalPrice, 
      cart,
      addToCartMessage: true,
      ...items, remaining
    })
  }

  removeItem = cartItems => { 
    const { items, cart, total } = this.state

    const removeItem = cart.filter(item => item.id !== cartItems.id)

    const itemId = items.find(item => item.name === cartItems.name).remaining++

    this.setState({
      removeFromCartMessage: true,
      total: total - cartItems.price,
      cart: removeItem, 
      ...items, remaining: itemId 
    })
  }

  render() {
    const { cart, items, total, addToCartMessage, removeFromCartMessage } = 
    this.state

    if (addToCartMessage || removeFromCartMessage) {
      setTimeout(() => {
        this.setState({ 
          addToCartMessage: false, 
          removeFromCartMessage: false 
        })
      }, 1000)
    }

    const filteredItems = items.filter(item => 
      item.name.includes(this.state.searchTerm))

    return (
      <div className="App">
        {cart.length === 0 ? <h3>No items in cart</h3> : (
          <div>
            <h1>Cart:</h1>
            {cart.map(items => (
              <div key={items.id}>
                <h1>{items.name} x 3</h1>
                <p>${items.price}</p>
                <button onClick={() => this.removeItem(items)}>Remove From Cart</button>
              </div>
            ))}
          </div>
        )}

        <hr />

        <input 
          type="text"
          placeholder="Search for an item..." 
          onChange={e => this.setState({ searchTerm: e.target.value })} 
          value={this.state.searchTerm} 
        />

        {filteredItems.map(item => (
          <div key={item.id}>
            <h1>{item.name}</h1>
            <p>Price: ${item.price}</p>
            {item.remaining === 0 ?  <p>Sold Out</p> : (
              <div>
                <p>Remaining: {item.remaining}</p>
                <button onClick={() => this.addItem(item)}>Add To Cart</button>
              </div>
            )}
          </div>
        ))}

        { total !== 0 ? <h1>Total ${total}</h1> : <h1>Total $0</h1> }
        { addToCartMessage && <h1>Item successfully added!</h1> }
        { removeFromCartMessage && <h1>Item successfully removed!</h1> }
      </div>
    )
  }
}

export default App

Ответы [ 3 ]

0 голосов
/ 02 июня 2018

Храните ваши товары в обычном объекте по идентификатору.

1: {
    id: 1,
    name: 'chocolate'
}

Сохраняйте корзину в виде массива идентификаторов.

[1, 1, 1]

В вашем компоненте групповые идентификаторы корзина массива по идентификаторучтобы получить счетчик, и искать объект корзины по ID, чтобы получить его данные.

Вычисленные данные должны быть вычислены, а не сохранены.

Вот несколько полностью непроверенных, непроверенных, код, показывающий выполненные вычисленияв функции рендеринга:

class App extends Component {
    state = {
        cart: [],
        items: [
            { id: uuid(), name: 'chocolate', price: 10, available: 5 },
            { id: uuid(), name: 'strawberry', price: 50, available: 10 },
            { id: uuid(), name: 'banana', price: 43, available: 20 }
        // Convert to an object of { id: { id, name, price } }
        ].reduce((memo, item) => ({
            ...memo,
            [item.id]: item
        }), {}),
    }

    addItem = id => {
        const { cart, } = this.state

        this.setState({
            cart: [ ...cart, id ]
        })
    }

    removeItem = removeId => {
        const { cart, } = this.state
        this.setState({
            cart: cart.filter(({ id }) => id !== removeId)
        })
    }

    render() {
        const { cart, items, total, addToCartMessage, removeFromCartMessage } = this.state

        // Given an array of item IDs in our cart, group them into an object
        // with the total count and price for each item, and overall count
        const accumulatedItems = items.reduce((memo, item) => {
            const { id, price } = item;
            const { count, price, } = memo[id] || {};
            return {
                ...memo,
                cartTotal: memo.cartTotal + price,
                [id]: {
                    count: (count || 0) + 1,
                    total: (price || 0) + price,
                }
            };
        // Starting object to reduce
        }, {
            cartTotal: 0,
        });

        return (
            <div className="App">
                {cart.length === 0 ? <h3>No items in cart</h3> : (
                    <div>
                        <h1>Cart:</h1>
                        {Object.keys(accumulatedItems).sort().map(id => (
                            <div key={id}>
                                <h1>{items[id].name} x {accumulatedItems[id].total}</h1>
                                <p>${accumulatedItems[id].total}</p>
                                <button onClick={() => this.removeItem(id)}>Remove From Cart</button>
                            </div>
                        ))}
                    </div>
                )}
            </div>
        );
    }
}

Жонглирование вычисленными данными и изменение состояния, например remaining, добавляет значительную логическую сложность вашему приложению.Вам следует начинать беспокоиться только о попытках сохранить / кэшировать / запомнить вычисленное состояние, когда возникнут проблемы с производительностью.Вычисление итогов в функции рендеринга является хорошим первым проходом.

В React в дикой природе есть решения, такие как reselect , которые технически не требуют редукции.Это просто функции кэширования, которые принимают данное состояние и производят вычисленный вывод, и только пересчитывают вывод, если ввод изменяется.

0 голосов
/ 03 июня 2018

Используя предложение @devserkan о реструктуризации состояния корзины и предложение @Andy Ray о реструктуризации состояния элементов, я настроил свое состояние так:

state = {
  items: {
    1: {
      id: 1, name: 'chocolate', price: 10, available: 5
    },  

    2: {
      id: 2, name: 'strawberry', price: 10, available: 10
    },

    3: {
      id: 3, name: 'banana', price: 10, available: 20
    }
  }

  cart: {
    ids: [],
    quantity: { 1: 0, 2: 0, 3: 0 }
  }
}

Затем я отправил элементыи добавил функцию onClick (addItem) для обработки некоторых вызовов setState:

render() {
  const { cart, items } = this.state

return (
  <div>
    <h1>Shopping Area</h1>
    {Object.values(items).map(item => (
      <div key={item.id}>
        <h2>{item.name}</h2>
        <h2>{item.price}</h2>
        <button onClick={() => this.addItem(item)}>Add To Cart</button>
      </div>
    ))}

В моей функции addItem я продолжил и установил состояние cart, чтобы выдвинуть идентификатор элементаи обновите количество для этого идентификатора:

addItem = item => {
  const { cart, items } = this.state

  this.setState({
    cart: {
      ...cart,
      // Push item id to ids array inside cart state
      ids: [...cart.ids, item.id],
      quantity: {
        ...cart.quantity,
        // Update quantity of the specific id pushed by 1
        [item.id]: cart.quantity[item.id] + 1
      }
    }
  })
}

Наконец мне пришлось отрендерить раздел корзины: я сделал это, проверив, не является ли массив cart.ids пустым, и сделал еще одну проверкуотображать только элемент с количеством больше 0. Если мы не выполняем эту проверку, каждый раз, когда мы нажимаем элемент, он добавляет все 3 сразу, и мы хотим, чтобы отображался только этот конкретный элемент.

 {cart.ids.length !== 0 ? Object.keys(items).map(id => (
   <div key={id}>
     // Check to see if quantity for that item is > 0
     {cart.quantity[id] > 0 && (
       <h1>{items[id].name} x {cart.quantity[id]}</h1>
     )}
   </div>
 )) : <h1>No Items In Your Cart</h1>}

Полный код (без цены / остатка)

export default class App extends Component {
  state = {
    cart: {
      ids: [],
      quantity: {
        1: 0, 
        2: 0, 
        3: 0
      }
    },

    items: {
      1: {
        id: 1, name: 'chocolate', price: 10, available: 5
      },  

      2: {
        id: 2, name: 'strawberry', price: 10, available: 10
      },

      3: {
        id: 3, name: 'banana', price: 10, available: 20
      }
    }
  }

  addItem = item => {
    const { cart, items } = this.state

    this.setState({
      cart: {
        ...cart,
        ids: [...cart.ids, item.id],
        quantity: {
          ...cart.quantity,
          [item.id]: cart.quantity[item.id] + 1
        }
      }
    })
  }

  removeItem = removeId => {
    const { cart } = this.state
    this.setState({
      cart: cart.filter(({ id }) => id !== removeId)
    })
  }

  render() {
    const { cart, items, total, addToCartMessage, removeFromCartMessage } = 
    this.state

    return (
      <div className="App">
        <h1>Shopping Area</h1>
        {Object.values(items).map(item => (
          <div key={item.id}>
            <h2>{item.name}</h2>
            <h2>{item.price}</h2>
            <button onClick={() => this.addItem(item)}>Add To Cart</button>
          </div>
        ))}

        <hr style={{'marginTop': '200px'}} />

        <h1>Cart</h1>

        {cart.ids.length !== 0 ? Object.keys(items).map(id => (
          <div key={id}>
            {cart.quantity[id] > 0 && (
              <h1>{items[id].name} x {cart.quantity[id]}</h1>
            )}
          </div>
        )) : <h1>No Items In Your Cart</h1>}
      </div>
    )
  }
}

Большое спасибо @Andy Ray и @devserkan за предложения.

0 голосов
/ 02 июня 2018

Может быть, вы можете сделать это так, как вы думаете и объяснить в своем вопросе.Есть несколько способов сделать это, и каждый делает это так, как им нравится, или как легко они делают и поддерживают это.

  • Вместо того, чтобы вставлять сам элемент, возможно, вы можете держать объект для каждого элемента в вашеммассив с уникальным идентификатором элемента.Этот объект также может содержать количество.Затем вы можете сгенерировать информацию о карте с помощью этого уникального идентификатора.

Пример:

cart: [
          { id: uniqeId, quantiy: 1 },
          { id: uniqeId, quantiy: 6 },
      ]

После добавления элемента на карту вы можете перейти и просто изменить количество соответствующего объекта.Но для этого вы должны найти элемент в этом массиве, а затем изменить количество, как вы предполагаете.

  • Вы можете иметь идентификаторы элементов в вашем объекте корзины (на этот раз не в массиве) в виде массива, но этораз вы разделяете количество и удерживаете его как объект по идентификатору товараТаким образом, после добавления элемента в список массива идентификатора корзины вы также можете изменить количество объекта элемента.С этим методом вам не нужно ничего искать, но вам нужно изменить две части информации.

Как:

cart: {
          ids: [ uniqueId, uniqueId ],
          quantity: { uniqueId: 1, uniqueId: 6 }
      } 
  • Или вы можете сделать какВы описываете, просто добавьте элемент, но перед этим проверьте, есть ли элемент уже там.Например, фильтрация по идентификатору.Но с этой техникой могут быть некоторые глюки.Когда вы добавляете такие товары, например, с указанием цены, остатка и т. Д., Вы также должны поддерживать состояние своей корзины в соответствии с состоянием товара.Например, что произойдет, если вы захотите изменить цену товара?Или что, если есть другой способ (как-то) изменить оставшееся другое, кроме добавления товаров в корзину?Но, если вы играете только с идентификаторами, вы можете извлечь эту информацию из вашего единственного состояния: items.

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

Кстати, не используйте push для изменения вашего состояния.Это изменяет ваше состояние, и это не рекомендуется.Используйте что-то вроде concat или оператор распространения для создания нового состояния.

item = { foo: bar, fizz: buzz }
// now setting state
cart: [ ...this.state.cart, item ]

И попробуйте использовать функцию обратного вызова в setState (так как это асинхронная операция), если изменение вашего состояния зависит от предыдущего состояния,

this.setState( prevState => ( {
    cart: [ ...prevState.cart, item ],
} ) )
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...