从React数组中删除项时出现意外行为

3

我已经费尽心思搜索答案,但我需要一些人类的帮助。

我有一个简单的表单,用户可以添加额外的字段。用户还可以删除不再相关的字段。

问题出在删除字段上。

这些字段是从组件状态中的数组呈现出来的,但当我尝试从数组中删除一个项目时,总是删除最后一个项目。

import React, { Component } from "react";

class Form_newPriority extends Component {
  constructor(props) {
    super(props);
    this.state = {
      listName: "",
      rows: [{ id: 1, fieldValue: "" }],
    };
  }

  addrow = () => {
    const rows = [...this.state.rows];
    rows.push({
      id: rows.length + 1,
      fieldValue: "",
    });
    this.setState({ rows: rows });
  };

  removeRow = (idx) => {
    const rows = [...this.state.rows];
    rows.splice(idx, 1);
    this.setState({ rows: rows });
  };

  render() {
    return (
      <div>
        <input
          className="w-full bg-white border border-alice px-4 py-2 mb-1 h-12 rounded-lg"
          type="text"
        ></input>
        <h2>Items:</h2>
        {this.state.rows.map((row, idx) => {
          return (
            <React.Fragment>
              <input
                className="w-half bg-white border border-alice px-4 py-2 mb-4 mr-4  h-12 rounded-lg"
                key={idx}
                type="text"
                placeholder={idx}
              ></input>
              <button
                className="mb-4 text-red-700 inline-block"
                onClick={() => {
                  this.removeRow(idx);
                }}
              >
                Remove
              </button>
            </React.Fragment>
          );
        })}
        <button
          className="bg-alice hover:bg-gray-400 text-c_base text-sm font-bold py-1 px-2 rounded block mr-4"
          onClick={this.addrow}
        >
          Add row
        </button>
      </div>
    );
  }
}

export default Form_newPriority;

我也尝试过

rows.filter()

但是这会产生相同的结果。

我对JS和React都比较新,所以我在这里做了一些愚蠢的事情吗?


你所分配给输入的值是映射元素的索引,你应该在状态中包含该索引的值,并基于该索引删除元素。参考@Józef Podlecki的答案。此外,在进行元素映射和返回渲染时,应使用key属性。 - ROOT
2个回答

2

看起来好像是删除了最后一个元素,因为你使用的是index作为标识符。

如果你使用递增的id,会更加合理。

示例

const { Component, useState, useEffect } = React;

class Form_newPriority extends Component {
  constructor(props) {
    super(props);
    this.state = {
      listName: "",
      counter: 1,
      rows: [{ id: 1, fieldValue: "" }],
    };
  }

  addrow = () => {
    const rows = [...this.state.rows];
    const counter = this.state.counter + 1;
    rows.push({
      id: counter,
      fieldValue: "",
    });
    this.setState({ counter, rows });
  };

  removeRow = (idx) => {
    const rows = [...this.state.rows];
    rows.splice(idx, 1);
    this.setState({ rows: rows });
  };

  render() {
    return (
      <div>
        <input
          className="w-full bg-white border border-alice px-4 py-2 mb-1 h-12 rounded-lg"
          type="text"
        ></input>
        <h2>Items:</h2>
        {this.state.rows.map((row, idx) => {
          return (
            <React.Fragment key={row.id}>
              <input
                className="w-half bg-white border border-alice px-4 py-2 mb-4 mr-4  h-12 rounded-lg" 
                type="text"
                placeholder={row.id}
              ></input>
              <button
                className="mb-4 text-red-700 inline-block"
                onClick={() => {
                  this.removeRow(idx);
                }}
              >
                Remove
              </button>
            </React.Fragment>
          );
        })}
        <button
          className="bg-alice hover:bg-gray-400 text-c_base text-sm font-bold py-1 px-2 rounded block mr-4"
          onClick={this.addrow}
        >
          Add row
        </button>
      </div>
    );
  }
}

ReactDOM.render(
    <Form_newPriority />,
    document.getElementById('root')
  );
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<div id="root"></div>


看起来你的例子遇到了我遇到的相同问题。你能解释一下它应该如何工作吗? - Nick
这不是同一个问题。我正在使用id作为key - Józef Podlecki
@JózefPodlecki 不,你正在使用数组索引 idx:<React.Fragment key={idx}>。如果你把它改成 <React.Fragment key={row.id}>,我认为它会起作用。 - Brian Thompson
1
抱歉,我不太明白。如果您在示例中添加了3个输入,然后尝试删除第一个输入,则会删除最后一个输入。 - Nick
1
我的错,我改了键。 - Józef Podlecki

1
这是一个相当常见的问题,有时很难理解。它与React如何管理动态元素有关,例如从map返回时。
问题
你可能已经看到了一个警告,提示你在从map返回时应该使用key。当没有给出key时,React会自动使用数组元素的索引。这就是问题的根源。
React使用这个key来跟踪每个元素。所以假设你有一个返回3个
的映射。React将用它的key标记每个
,以便它知道未来需要更新哪些内容。结果将是以下内容:
<div key={0} val='a' />
<div key={1} val='b' />
<div key={2} val='c' />

这对添加和更新来说很好用。如果你更新索引为1的条目,React会通过key标识出第二个元素并进行更新。
棘手的情况(就像你发现的那样)是删除。
假设你删除了中间的元素。结果数组现在只包含2个位置。现在你的JSX将如下所示:
<div key={0} val='a' />
<div key={1} val='c' />

也许你已经看到发生了什么?你删除了中间的元素,但对于React来说,你删除了最后一个元素并更新了第二个元素。为什么?因为key是基于索引的,而索引已经改变了。React将卸载具有key为2的div,并保留具有key为1的div。第二个div没有被删除,而是用新的prop重新呈现了。
解决方案
根本性的解决方法是为每个元素提供一个唯一的key,这个key不会随着时间的推移而改变。根据您的使用情况,实现可能会有所不同。
例如,如果您的列表保证每个条目都有一个唯一的值,您可以使用该值。这是最简单的解决方案,但并不总是可用的。
我过去使用的一个解决方案是,在状态中保持一个计数器,每次添加一个元素时都会增加。然后,我使用计数器为每个元素创建一个id。这样,如果删除一个元素,id就不会改变。
警告
一个常见的错误是在遇到这个问题时,使用帮助函数来为每个元素生成完全随机的key。这可能会解决当前的问题,但这是一种非常糟糕的做法,可能会导致未来出现bug。如果key在每次渲染时更改,元素将在每次渲染时卸载和重新渲染。这对于像这样简单的div示例没有任何副作用,但如果您的元素是具有状态的另一个组件,则会失去其状态。如果您的元素是输入框,则可能会在每次更新时失去焦点。虽然这很诱人,但不要使用随机生成的数字作为key

1
谢谢您抽出时间来解释这个问题。我想我明白了。 - Nick

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接