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