Failed to execute 'removeChild' on 'Node': 要删除的节点不是该节点的子节点。在输入框输入时出现此错误

问题描述 投票:0回答:2

我正在尝试制作一个待办事项应用程序,其中我在删除元素时创建了删除功能,该元素被成功删除但是一旦我在输入框中键入我就会收到此错误。 无法在“节点”上执行“removeChild”:要删除的节点不是该节点的子节点。

import { React, useState } from 'react'
import "./todo.css"

function Todo() {
    const [value, setValue] = useState("")  //input value
    const [items, setitems] = useState([])  //items to be added

    function handleClick() {
        //  Adding items in a item array
        setitems(items.concat(value))
        setValue("")
    }

    function handleChange(e) {
        // Fetching input value
        setValue(e.target.value)
    }



    return (
        <>
            <div className='container'>
                <h1 id='mainhead'>Todo App</h1>
            </div>

            <div className='container1'>
                <input onChange={handleChange} value={value} placeholder='Enter a  task' type="text" />
                <button onClick={handleClick}>Add Item</button>
            </div>

            {/* mapping all the items */}

            {items.length !== 0 ? items.map((e) => {
                return <div className='item' key={e}><label>Task {items.indexOf(e) + 1}:</label> {e}

                    <button style={{float:"right" , backgroundColor:"red" , color:"white" , width:"80px" , height:"30px"}} onClick={()=>{

                        const child = document.getElementById("delete" + items.indexOf(e)) // accessing child
                        console.log(child)
                        child.parentElement.remove() //Deleting parent element
                        items.splice(items.indexOf(e),1) //removing element from items
                        setitems(items)   // updating items

                    }} id = {"delete" + items.indexOf(e)}>Delete</button> </div>
            })
                : null}



        </>
    )
}

export default Todo

我尝试了一切,但没有任何效果可以帮助我处理这个错误

javascript reactjs dom
2个回答
0
投票

不是直接更改DOM,而是使用状态来管理项目。要从列表中删除项目,您可以过滤该项目并设置新状态。

function handleDelete(index) {
  // Filter the items array and remove the item at the given index
  const newItems = items.filter((_, i) => i !== index);
  setItems(newItems);
}

handleDelete() 函数替换Delete 按钮onClick 处理程序。所以渲染图会这样变化:

{/* mapping all the items */}
{
  items.length !== 0
    ? items.map((e, index) => {
        return (
          <div className="item" key={index}>
            <label>Task {index + 1}:</label> {e}
            <button
              style={{
                float: "right",
                backgroundColor: "red",
                color: "white",
                width: "80px",
                height: "30px",
              }}
              onClick={() => handleDelete(index)}
              id={"delete" + index}
            >
              Delete
            </button>
          </div>
        );
      })
    : null;
}

顺便说一句: 上面的代码使用索引进行过滤并作为虚拟 DOM 元素键,只是为了尽可能接近您的示例。为元素设置唯一 ID 是一种很好的做法。这可以提高性能,帮助避免错误,并使调试更容易。


0
投票

您基本上拥有组件所需的所有位 - 最重要的是保存待办事项的状态。必要的更改是当您单击任何删除按钮时调用的处理程序应该简单地

filter
从状态中取出正确的待办事项。

促进这一点的最好方法是确保您添加到状态的待办事项是 objects,并且每个对象都有一个 id。然后将该 id 添加到 data 属性,以便在调用处理程序时可以使用该 id 从状态中过滤正确的对象。

我已经对您的代码进行了一些尝试 - 重命名,并将待办事项放在表格中 - 但希望评论会有所帮助。

const { useState } = React;

function Todos() {
  
  const [ text, setText ] = useState('');
  const [ todos, setTodos ] = useState([]);

  // You should be setting state without
  // mutating the array. Here I'm adding a new
  // object with an id to the state (we pass in 
  // the previous state, and then add the new object
  // to that
  function handleAdd(e) {
    setTodos(prev => {
      return [
        ...prev,
        { id: prev.length + 1, text }
      ];
    });
    setText('');
  }

  function handleChange(e) {
    setText(e.target.value);
  }

  // You can use the id from the element's dataset
  // to allow you to filter out that object from the todos
  // array, and then set the todos state with that 
  // filtered array. Note the id from the dataset will be a
  // string so we need to coerce it to a number before we
  // do a strict equality comparison
  function handleDelete(e) {
    const { dataset: { id } } = e.target;
    const filtered = todos.filter(todo => Number(id) !== todo.id);
    setTodos(filtered);
  }

  // For this example I've put the todos in a table
  // for easier visualisation. You'll note that the todo
  // id is added to a data attribute on the each delete button
  // so that it can be used in the delete handler
  return (
    <main>

      <header>
        <h1>Todo App</h1>
      </header>

      <section className="container1">
        <input onChange={handleChange} value={text} placeholder="Enter a  task" type="text" />
        <button onClick={handleAdd}>Add Item</button>
      </section>

      <table>
        <tbody>
        {todos.length ? todos.map(todo => {
          return (
            <tr className="item" key={todo.id}>
              <td class="id">{todo.id}</td>
              <td class="text">{todo.text}</td>
              <td>
                <button
                  data-id={todo.id}
                  class="delete"
                  onClick={handleDelete}
                >Delete
                </button>
              </td>
            </tr>
          );
        }) : null}
        </tbody>
      </table>
    </main>

  );

}

ReactDOM.render(
  <Todos />,
  document.getElementById('react')
);
table { width: 80%; margin-top: 1em; }
td.id { width: 10%; }
td.text { width: 100%; }
td.delete { float: right; }
.item { display: flex; flex-direction: row; border: 1px solid lightgray; }
.delete { background-color: salmon; margin-left: 1em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

© www.soinside.com 2019 - 2024. All rights reserved.