React-Draft-Wysiwyg 文本框的 React 动态列表在删除后未正确更新

React dynamic list of React-Draft-Wysiwyg text boxes not updating correctly after remove

Codesandbox link: https://codesandbox.io/s/tender-meadow-un6ny7?file=/src/index.js

我正在创建一个基本应用程序,您可以在其中通过 react-draft-wysiwyg 库动态添加文本框。

  return (
    <div className="editor">
      <Editor
        //Update and show text box content
        editorState={editorState}
        onEditorStateChange={(editorState) => {
          let html = stateToHTML(editorState.getCurrentContent());
          console.log(html);
          setHtml(html);
          props.setContent(editorId, html, editorState);
          setEditorState(editorState);
        }}
        //Display toolbar on top
        toolbar={{
          inline: { inDropdown: true },
          list: { inDropdown: true },
          textAlign: { inDropdown: true },
          link: { inDropdown: true },
          history: { inDropdown: true },
        }}
      />
    </div>
  );
};

文本框通过 React 状态挂钩进行存储和更新。一个控制每个文本框的实际呈现状态 (editorState),另一个存储每个文本框内容的原始 html (html)。状态挂钩已经定义了添加、删除和编辑行为。

const EditorContent = (props) => {
  const [editorState, setEditorState] = useState(props.content);
  const [html, setHtml] = useState("");

  useEffect(() => {
    console.log("loaded number " + props.id);
  });

function App() {
  //Declaration of editorList array and setList setter
  const [editorList, setEditorList] = useState([
    { id: 0, html: "", content: EditorState.createEmpty() },
  ]);
  const [numberofEditors, setNumberOfEditors] = useState(0);

  //Lets you view editorList content in inspect tab
  console.log(editorList);

  //Used to find specific instance, not used in current code
  //Refer to video, forgot what it's used for

  const handleServiceChange = (e, index) => {
    const { name, value } = e.target;
    console.log(name, value);
    const list = [...editorList];
    list[index][name] = value;
    //setEditorList(list);
  };

  //Handles remove function
  const handleEditorRemove = (id) => {
    console.log("Remove id " + id);
    // remove editor with designated id
    let list = editorList.filter((editor) => {
      return editor.id !== id;
    });
    console.log("Updated List: " + list);
    setEditorList(list);
  };

  const handleEditorAdd = (id) => {
    console.log(id);
    setEditorList([
      ...editorList,
      { id: id, html: "", content: EditorState.createEmpty() },
    ]);
    setNumberOfEditors(numberofEditors + 1);
  };

  const setEditorContent = (id, html, editorState) => {
    console.log(id, html);
    // deep copy array
    let editorsCopy = [];
    for (let editor of editorList) {
      editorsCopy.push(editor);
    }
    const index = editorsCopy.findIndex((editor) => {
      return editor.id === id;
    });
    editorsCopy[index].html = html;
    editorsCopy[index].content = editorState;

    setEditorList(editorsCopy);
  };

每个文本框也呈现有两个按钮,一个用于添加另一个框,另一个用于删除该特定组件。

  return (
    <form className="App" autoComplete="off">
      <div className="form-field">
        <label htmlFor="editor">Editor(s)</label>
        {editorList.map((editor, index) => (
          <div key={index} className="services">
            <div className="first-division">
              <EditorContent id={editor.id} setContent={setEditorContent} />
              {editorList.length - 1 === index && editorList.length < 4 && (
                <button
                  type="button"
                  onClick={() => {
                    handleEditorAdd(numberofEditors + 1);
                  }}
                  className="add-btn"
                >
                  <span>Add an Editor</span>
                </button>
              )}
            </div>
            <div className="second-division">
              {editorList.length !== 1 && (
                <button
                  type="button"
                  onClick={() => {
                    handleEditorRemove(editor.id);
                  }}
                  className="remove-btn"
                >
                  <span>Remove</span>
                </button>
              )}
            </div>
          </div>
        ))}
      </div>
      <div className="output">
        <h2>Output</h2>
        {editorList &&
          editorList.map((editor, index) => (
            <ul key={index}>{editor.service && <li>{editor.content}</li>}</ul>
          ))}
      </div>
    </form>
  );
}

添加新文本框和更新存储的 html 效果很好。但是,当我尝试删除特定文本框时,无论我使用哪个删除按钮 select,最后一行总是被删除。我尝试为每个文本框添加 id 键并进行拼接,但似乎没有任何东西可以正确呈现更改,尽管 HTML 数组显示了正确的文本。

我是否错误地更新了状态?或者只是渲染不正确?如有任何意见,我们将不胜感激。

我在你的代码中看到的唯一错误是你正在使用 key={index} whilie渲染列表项,并根据官方文档

选择键的最佳方法是使用一个字符串来唯一标识列表项 siblings.Most 通常您会使用数据中的 ID 作为键

因此,您应该使用编辑器的 ID 而不是使用索引,这将使它正常工作,例如

{editorList.map((editor, index) => (
      <div key={editor.id} className="services">
        <div className="first-division">
          <EditorContent id={editor.id} setContent={setEditorContent} />
          {editorList.length - 1 === index && editorList.length < 4 && (
            <button
              type="button"
              onClick={() => {
                handleEditorAdd(numberofEditors + 1);
              }}
              className="add-btn"
            >
              <span>Add an Editor</span>
            </button>
          )}
        </div>
        <div className="second-division">
          {editorList.length !== 1 && (
            <button
              type="button"
              onClick={() => {
                handleEditorRemove(editor.id);
              }}
              className="remove-btn"
            >
              <span>Remove</span>
            </button>
          )}
        </div>
      </div>
    ))}