«Внимание: У каждого ребенка в списке должна быть уникальная «ключевая» опора» при добавлении нового элемента в список

#reactjs #typescript #mongodb #list #ids

Вопрос:

Я разрабатываю список элементов, используя React, Express (axios) и MongoDB.

Элементы массива списка извлекаются из MongoDB, отображаются в компоненте, и пользователь может добавлять новые элементы (и сохранять их в MongoDB).

Проблема в том, что Warning: Each child in a list should have a unique "key" prop" отображается, потому что новый элемент добавляется в список с помощью id: Math.random() атрибута (хотя эти идентификаторы не передаются в БД).

App.tsx (ЗДЕСЬ список элементов извлекается с идентификаторами, сгенерированными MongoDB)

 export default function App() {
    const [ExpenseAndAmountList, setExpenseAndAmountList] = useState<
      Array<{
        id: number,
        expenseTitle: string,
        expenseAmount: string,
      }>
    >([]);


 useEffect(() => {
    const expensesListResp = async () => {
      await axios.get('http://localhost:4000/app/expenseslist')
      .then(
        response => setExpenseAndAmountList(response.data amp;amp; response.data.length > 0 ? response.data : []));
    }
    expensesListResp();
  }, []);

    return (
      <div className="App">
        <ExpenseAmountInputContainer 
          expenseAndAmountList={ExpenseAndAmountList}
          setExpenseAndAmountList={setExpenseAndAmountList}
          setTotalExpensesAmount={setTotalExpensesAmount}
          totalExpenses={TotalExpensesAmount}
        />

        <DynamicList 
          expenseAndAmountList={ExpenseAndAmountList} 
          currencySymbol={Currency}
          setExpenseAndAmountList={setExpenseAndAmountList}
        />
      </div>
    );
}

 

ExpenseAmountInputContainer.tsx (ЗДЕСЬ элементы размещаются в списке MongoDB без какого-либо идентификатора)

 interface Props {
    expenseAndAmountList: Array<ExpenseAndAmountObject>;
    setExpenseAndAmountList: (value: Array<ExpenseAndAmountObject>) => void; 
}

const ExpenseAmountInputContainer: React.FC<Props> = (
        {
            expenseAndAmountList, 
            setExpenseAndAmountList,
        }: Props
    ) => {
    
    const [Expense, setExpense] = useState<string>('');
    const [Amount, setAmount] = useState<string>('');

    const AddItemToList = () => {
        if (Expense !== '' amp;amp; Amount !== '' amp;amp; Number(Amount) > 0) {

            axios.post('http://localhost:4000/app/expenseslist', 
            {
                expenseTitle: Expense,
                expenseAmount: Amount
            });
            
            setExpense("");
            setAmount("");

            const expensesListResp = async () => {
                await axios.get('http://localhost:4000/app/expenseslist')
                .then(
                response => setExpenseAndAmountList(response.data amp;amp; response.data.length > 0 ? response.data : []));
            }
            expensesListResp();
    }

    return (
        <div>
            <InputItem 
                onChange={setExpense} 
                onBlur={setExpense} 
                title="Expense" 
                type="text" 
                placeholder="Item title" 
                value={Expense}
            />
            <InputItem 
                onChange={setAmount}  
                onBlur={setAmount}  
                title="Amount" 
                type="number" 
                placeholder="Expense cost" 
                value={Amount}
            />
            <AddButton 
                onClick={AddItemToList} 
                content="Add expense"

            />
        </div>
    );
};

export default ExpenseAmountInputContainer;
 

ExpenseAndAmountObject.tsx (интерфейс, используемый в ExpenseAmountInputContainer.tsx)

 export interface ExpenseAndAmountObject {
    id: number,
    expenseTitle: string,
    expenseAmount: string,
}
 

DynamicList.tsx

 import { ExpenseAndAmountObject } from '../ExpenseAndAmountObject';
  interface ListItemsArray {
    expenseAndAmountList: Array<ExpenseAndAmountObject>;
    currencySymbol: string;
    setExpenseAndAmountList: (value: Array<ExpenseAndAmountObject>) => void;
  }

  const DynamicList: React.FC<ListItemsArray> = (
    {
      expenseAndAmountList, 
      currencySymbol,
      setExpenseAndAmountList
    }: ListItemsArray) => {

    return (
        <>
            <List>
                {expenseAndAmountList.map(item => (
                  <ExpensesListItem
                    expenseTitle={item.expenseTitle} 
                    expenseAmount={item.expenseAmount}
                    currencySymbol={currencySymbol}
                    item={item}
                    items={expenseAndAmountList}
                    setExpenseAndAmountList={setExpenseAndAmountList}
                  />
                ))} 
            </List>
        </>
      );
  }
  
export default DynamicList;
 

Как можно было бы решить эту проблему?

Комментарии:

1. Где вы задаете свойства идентификатора элемента? Вы устанавливаете их в бэкэнде? Math.random на самом деле это не очень хороший вариант использования для создания уникальных идентификаторов.

2. Мой комментарий вводил в заблуждение, я им не пользовался Math.random . ЗА свой счет элементы Mountinputcontainer.tsx разносятся в MongoDB без каких-либо идентификаторов (поскольку они генерируются в MongoDB)

3. Как бы то ни было, все еще неясно, куда вы добавляете какие-либо id свойства при добавлении новых элементов в список. Где генерируются id s? Где возникает ошибка? Похоже, вы генерируете их в своем бэкэнде. Если они не уникальны, то у вас возникнет эта проблема с интерфейсом. Можем ли мы увидеть соответствующий внутренний код?

4. В DynamicList.tsx вы устанавливаете ключевые параметры для компонентов, созданных с использованием map() ?

5. Я нигде не устанавливал ключевую опору, и я просто добавил атрибут key={item.id} в <List> . Я пытаюсь этого добиться item.id будет _id сгенерирован MongoDB (может быть, это неправильный подход..?)

Ответ №1:

Вам нужно передать реквизит key при визуализации списков.

 import { ExpenseAndAmountObject } from '../ExpenseAndAmountObject';
  interface ListItemsArray {
    expenseAndAmountList: Array<ExpenseAndAmountObject>;
    currencySymbol: string;
    setExpenseAndAmountList: (value: Array<ExpenseAndAmountObject>) => void;
  }

  const DynamicList: React.FC<ListItemsArray> = (
    {
      expenseAndAmountList, 
      currencySymbol,
      setExpenseAndAmountList
    }: ListItemsArray) => {

    return (
        <>
            <List>
                {expenseAndAmountList.map(item => (
                  <ExpensesListItem

                    key={} // <-----  SOME UNIQUE ID HERE!!!!!!!!

                    expenseTitle={item.expenseTitle} 
                    expenseAmount={item.expenseAmount}
                    currencySymbol={currencySymbol}
                    item={item}
                    items={expenseAndAmountList}
                    setExpenseAndAmountList={setExpenseAndAmountList}
                  />
                ))} 
            </List>
        </>
      );
  }
  
export default DynamicList;
 

Пожалуйста, ознакомьтесь с документацией, объясняющей такое поведение https://reactjs.org/docs/lists-and-keys.html#keys

Комментарии:

1. Я добавил атрибут key={item.id} <List> в соответствии с вашим примером. item.id предполагается _id , что он генерируется MongoDB (я проверил, что в динамическом списке компонентов массив элементов из MongoDB доступен, т. Е. равен [{_id: "60c970297ba73b083ec13dbc", expenseTitle: “first_title, expenseAmount: "1", __v: 0},{_id: "60c9782dae534110361f359a", expenseTitle: “second”title, expenseAmount: “2”, __v: 0}] ). Однако, когда я проверяю значения реквизитов элементов в ExpensesListItem, значение key={item.id} equals to undefined . Может быть, вы знаете, почему? Заранее спасибо!