Пропадает компонент React при изменении состояния

Помогите, пожалуйста, разобраться, уже сутки не могу понять что к чему. Есть карточки товаров, при клике на кнопку в карточке, товар пушится в массив, из этого массива другой компонент(корзина) отрисовывает собственно тот товар, на который был совершен клик(добавить в корзину). То есть банальное добавление товаров в корзину, но, чтобы в корзину не добавлялось 2 одинаковых товара, я написал проверку и если такой товар уже присутствует в массиве, то в корзине должно меняться только цифра в строке "Количество"

let basketItem = [];
const [quantity, setQuantity] = useState(1);
const addItem = (item) => {
  basketItem.includes(item) ? setQuantity(quantity + 1) : basketItem.push(item);
}

Пропсами всё передается в шапку, где находится компонент корзины

return (
<div className='app'>
  <Header {...{
    expand: 'md',
    fixed: 'top',
    color: 'light'
  }}
  cities={cities}
  basketItem={basketItem}
  quantity={quantity}
  />

Вот кнопка, по которой всё происходит

<Button onClick={() => props.addItem(item)}>

А вот код компонента Корзины

function Basket(props) {
  const [isOpen, setIsOpen] = useState(false);
  const toggle = () => setIsOpen(!isOpen)
  let arrItems = props.basketItem;
  console.log(props.quantity)

  function renderItems(arr) {
    const items =  arr.map((item, i) => {
      console.log(props.basketItem)
      return (
        <div
          key={i}>
          <Row>
            <Col sm="12">
              <Card body>
                <CardTitle tag="h5">
                  {item.name} | Количество: {props.quantity}
                </CardTitle>
                <Button>
                  Go somewhere
                </Button>
              </Card>
            </Col>
          </Row>
        </div>
      )
    });

    return (
      <>
        {items}
      </>
    )
  }

  const itemsDone = renderItems(arrItems)
  return (
    <>
      <img src={basket} className="basket" onClick={toggle} />
      <Offcanvas toggle={toggle}
                 isOpen={isOpen}
                 direction="end"
                 scrollable>
        <OffcanvasHeader toggle={toggle}>
          Корзина
        </OffcanvasHeader>
        <OffcanvasBody>
          {itemsDone}
        </OffcanvasBody>
      </Offcanvas>
    </>
  );
}

export default Basket;

Проблема в том, что если товар добавлен в Корзину, то повторный клик по "Добавить в корзину" удаляет из корзины все товары, но если кликнуть еще раз, то товары снова появляются с уже обновленным и актуальным стейтом(количество: 2) Вообще не понимаю почему так происходит, почему для нормального отображения на кнопку нужно кликать 2 раза. Буду очень признателен за подсказки.

Товар - это объект. Пример товара:

{
  name: "shovel1",
  top: 1,
  img: "https://remitek.ru/files/2020/09_22/14_09/u_files_store_1_1373036.jpg",
  type: "Bayonet",

},

Карточка товара:

<Card
                  style={{
                    width: '18rem'
                  }}
                >
                  <img
                    alt="Sample"
                    src={item.img}
                  />
                  <CardBody>
                    <CardTitle tag="h5">
                      {item.name}
                    </CardTitle>
                    <CardText>
                      Some quick example text to build on the card title and make up the bulk of the card‘s content.
                    </CardText>
                    <Button onClick={() => props.addItem(item)}>
                      Button
                    </Button>
                  </CardBody>
                </Card>

Ответы (1 шт):

Автор решения: SwaD

React перерисовывает компонент, только в том случае, если изменились переменные состояния или пропс компонента.

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

const [basketItem, setBasketItem] = useState([]);
const [quantity, setQuantity] = useState(1);
const addItem = (item) => {
  if (basketItem.includes(item)) {
    setQuantity(quantity + 1)
  } else {
    setBasketItem(prev => {
      prev.push(item);
      return [ ...prev ];
    })
  }
}

А на quantity реагировал, потому как первый клик - пуш элемента и ничего не происходит, второй клик, пуша нет, но есть изменение состояния компонента.

И я бы изменил формат хранения данных в корзине на массив объектов, вот так:

const [basketItem, setBasketItem] = useState([]);
const addItem = (item) => {
  const elem = basketItem.find(i => i.name === item);
  if (elem) {
    elem.quantity += 1;
  } else {
    basketItem.push({
      name: item,
      quantity: 1
    });
  }
  setBasketItem([ ...basketItem ]);
}

Теперь у вас каждый товар будет содержать информацию о названии и количестве в корзине

UPD после обновления вопроса:

т.к. товар это изначально объект, то можно при добавлении товара в корзину, добавлять поле quantity.

const [basketItem, setBasketItem] = useState([]);
const addItem = (item) => {
  const elem = basketItem.find(i => i.name === item);
  if (elem) {
    elem.quantity += 1;
  } else {
    basketItem.push({ ...item, quantity: 1 });
  }
  setBasketItem([ ...basketItem ]);
}

В то же время, includes для объектов не подходит(конечно можно использовать для поиска одинаковых объектов, но в данном случае этого делать не стот)

→ Ссылка