Динамический список React текстовых полей React-Draft-Wysiwyg не обновляется правильно после удаления

Ссылка на Codesandbox: 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.info(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.info("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.info(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.info(name, value);
    const list = [...editorList];
    list[index][name] = value;
    //setEditorList(list);
  };

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

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

  const setEditorContent = (id, html, editorState) => {
    console.info(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 работает отлично. Однако, когда я пытаюсь удалить конкретное текстовое поле, независимо от того, какую кнопку удаления я выбираю, всегда удаляется последняя строка. Я попытался добавить ключи идентификатора для каждого текстового поля и сплайсинга, но, похоже, ничто не правильно отображает изменения, несмотря на то, что массив HTML отображает правильный текст.

Я неправильно обновляю состояние? Или просто рендерит неправильно? Мы будем признательны за любой вклад.

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
0
0
26
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

единственная ошибка, которую я вижу в вашем коде, это то, что вы используете key = {index} при рендеринге элементов списка и согласно официальному документу

Лучший способ выбрать ключ — использовать строку, которая однозначно идентифицирует элемент списка среди его братьев и сестер. Чаще всего в качестве ключей используются идентификаторы из ваших данных.

поэтому вместо использования индекса вы должны использовать идентификатор редактора, это заставит его работать правильно, например

{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>
    ))}

Вау, это действительно сработало! Был еще один поток с уникальными ключами, и я, должно быть, слишком зациклился на идентификаторах. Большое спасибо за быстрый ответ.

Dom 29.03.2022 19:15

Другие вопросы по теме