Home > Software engineering >  React dynamic list of React-Draft-Wysiwyg text boxes not updating correctly after remove
React dynamic list of React-Draft-Wysiwyg text boxes not updating correctly after remove

Time:03-30

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

I'm creating a basic application where you can dynamically add in text boxes via the react-draft-wysiwyg library.

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

The textboxes are stored and updated through a React state hook. One controls the actual rendered state of each textbox (editorState) and another stores the raw html of each textbox's contents (html). The state hooks already have defined behavior for adding, removing, and editing.

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

Each text box is also rendered with two buttons, one that lets you add another box, another to delete that specific component.

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

Adding new textboxes and updating the stored html works perfectly. However, when I attempt to remove a specific text box, no matter which remove button I select, the last row is always removed. I attempted to add id keys for each textbox, and splicing, but nothing seems to properly render the changes, despite the HTML array displaying the correct text.

Am I updating the state incorrectly? Or just rendering incorrectly? Any input would be appreciated.

CodePudding user response:

the only mistake i can see inside your code is you are using key={index} whilie rendering list items,and as per the official doc

The best way to pick a key is to use a string that uniquely identifies a list item among its siblings.Most often you would use IDs from your data as keys

so instead of using index you should use id of an editor this will make it work correctly ,like

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