Странное поведение при удалении элемента из массива div (React Hooks) - PullRequest
0 голосов
/ 29 марта 2020

Я пытаюсь понять странное поведение при удалении элемента из массива div. Что я хочу сделать, это создать массив div, представляющих список покупок. Каждая покупка имеет кнопку удаления, которая должна удалять только нажатую. Происходит следующее: при нажатии кнопки «Удалить» на покупке x удаляются все элементы с индексами, превышающими x.

Любая помощь будет полезна, включая советы по синтаксису:)

import React, { useState } from "react";


const InvestmentSimulator = () => {

  const [counter, increment] = useState(0);
  const [purchases, setPurchases] = useState([
    <div key={`purchase${counter}`}>Item 0</div>
  ]);

  function addNewPurchase() {
    increment(counter + 1);
    const uniqueId = `purchase${counter}`;

    const newPurchases = [
      ...purchases,
      <div key={uniqueId}>
        <button onClick={() => removePurchase(uniqueId)}>delete</button>
        Item number {uniqueId}
      </div>
    ];

    setPurchases(newPurchases);
  }

  const removePurchase = id => {
    setPurchases(
      purchases.filter(function(purchase) {
        return purchase.key !== `purchase${id}`;
      })
    );
  };

  const purchasesList = (
    <div>
      {purchases.map(purchase => {
        if (purchases.indexOf(purchase) === purchases.length - 1) {
          return (
            <div key={purchases.indexOf(purchase)}>
              {purchase}
              <button onClick={() => addNewPurchase()}>add</button>
            </div>
          );
        }
        return purchase;
      })}
    </div>
  );

  return <div>{purchasesList}</div>;
};
export default InvestmentSimulator;

Ответы [ 2 ]

0 голосов
/ 29 марта 2020

Есть несколько проблем с вашим кодом, поэтому я буду go просматривать их по одному:


Не хранить JSX в состоянии

Состояние для хранение сериализуемых данных, а не пользовательский интерфейс. Вы можете хранить числа, логические значения, строки, массивы, объекты и т. Д. c ... но не хранить компоненты.


Сохраняйте свой JSX простым

JSX, которым вы являетесь возвращение немного запутанно. Вы отображаете purchases, но затем возвращаете кнопку add, если это последняя покупка. Кнопка добавления не связана с отображением покупок, поэтому определите ее отдельно:

return (
    <div>
        // Map purchases
        {purchases.map(purchase => (
            // The JSX for purchases is defined here, not in state
            <div key={purchase.id}>
                <button onClick={() => removePurchase(purchase.id)}>
                    delete
                </button>
                Item number {purchase.id}
            </div>
        ))}
        // An add button at the end of the list of purchases
        <button onClick={() => addNewPurchase()}>add</button>
    </div>
)

Поскольку мы не должны хранить JSX в состоянии, в операторе return мы превращаем наши значения состояния в JSX.


Не давайте сбивающих с толку имен для функций установки.

Вы создали переменную состояния counter и назвали функцию установки increment. Это вводит в заблуждение - функция increment не увеличивает счетчик, а устанавливает счетчик . Если я вызываю increment(0), счетчик не увеличивается, он устанавливается на 0.

Соответствует функциям установки имен. В сообществе React принято считать, что функция setter имеет то же имя, что и переменная, которую она устанавливает, с префиксом слова "set" . Другими словами, ваше значение состояния counter, поэтому ваша функция установки должна называться setCounter. Это точное и наглядное описание того, что делает функция:

const [counter, setCounter] = useState(0)

Состояние обновляется асинхронно - не обрабатывайте его синхронно

В функции addNewPurchase вы получаете:

increment(counter + 1)
const uniqueId = `purchase${counter}`

Это не будет работать так, как вы ожидаете. Например:

const [myVal, setMyVal] = useState(0)

const updateMyVal = () => {
  console.log(myVal)
  setMyVal(1)     
  console.log(myVal)
}

Рассмотрим приведенный выше пример. Первый console.log(myVal) будет записывать 0 в консоль. Что вы ожидаете второго console.log(myVal), чтобы войти? Вы можете ожидать 1, но на самом деле он также регистрирует 0.

Состояние не обновляется, пока функция не завершит выполнение и компонент не выполнит повторную визуализацию, поэтому значение myVal никогда не изменится частично через функцию. Она остается неизменной для всей функции.

В вашем случае вы создаете идентификатор со значением old , равным counter.


. компонент

Вот обновленная версия вашего компонента:

const InvestmentSimulator = () => {
    // Use sensible setter function naming
    const [counter, setCounter] = useState(0)

    // Don't store JSX in state
    const [purchases, setPurchases] = useState([])

    const addNewPurchase = () => {
        setCounter(prev => prev + 1)
        setPurchases(prev => [...prev, { id: `purchase${counter + 1}` }])
    }

    const removePurchase = id => {
        setPurchases(prev => prev.filter(p => p.id !== id))
    }

    // Keep your JSX simple
    return (
        <div>
            {purchases.map(purchase => (
                <div key={purchase.id}>
                    <button onClick={() => removePurchase(purchase.id)}>
                        delete
                    </button>
                    Item number {purchase.id}
                </div>
            ))}
            <button onClick={() => addNewPurchase()}>add</button>
        </div>
    )
}

Заключительные мысли

Даже с этими изменениями компонент по-прежнему требует -design.

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

0 голосов
/ 29 марта 2020

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

const purchasesList = (
    <div>
      {purchases.map((purchase, i) => {
        const idx = i;
        if (purchases.indexOf(purchase) === purchases.length - 1) {
          return (
            <div key={idx}>
              {purchase}
              <button onClick={() => addNewPurchase()}>add</button>
            </div>
          );
        }
        return purchase;
      })}
    </div>
  );

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...