useState variable not latest version in delete function, React

Viewed 408

I have a list of div's that contain question fields. For every button click i add a new line of question fields and memorize in state the whole list and how many lines there are. I tried adding a delete button but when my delete functions it seems that the value from the state variable is remembered from when the line was made. How do i solve this so i can acces the full list in the HandleDelete function?

const OefeningAanvragenDagboek = () => {
const { t, i18n } = useTranslation()
const [questionCount, setQuestionCount] = useState(1)
const [questionList, setQuestionList] = useState([])

const createNewLine = () =>{
    var newLine=
        <div>
            <Field type="text" name={"vraag" + questionCount}/>
            <Field component="select" name={"antwoordMogelijkheid"+questionCount}>
                <option value="">...</option>
                <option value="open">{t('oefeningAanvragenDagboek.open')}</option>
                <option value="schaal">{t('oefeningAanvragenDagboek.scale')}</option>
            </Field>
            <Field type="text" name={"type"+questionCount}/>
            <button onClick={() => HandleDelete(questionCount-1)}>{t('assignmentCard.delete')}</button>
        </div>

      setQuestionList(questionList => [...questionList, newLine])
      setQuestionCount(questionCount+1)
  }

  const HandleDelete = (index)=> {
      console.log(questionList)
      // setQuestionList(questionList.splice(index, 1))

  }

  return (
      <div>
          <button onClick={createNewLine}>{t('oefeningAanvragenDagboek.addQuestion')}</button>
          {questionList}
      </div>
  )
}
3 Answers

Use functional setState as HandleDelete has closure on questionList

setQuestionList(questionList => questionList.splice(index, 1))

Both state and props received by the updater function are guaranteed to be up-to-date.

Primarily addressing the issue on the OP's comments section, at the time of this writing which was given a bounty in addition to the question.

The SandBox with the issue: https://codesandbox.io/s/charming-architecture-9kp71?file=/src/App.js

Basically, the solution to this issue is to perform all operations on the parameter of the callback function. In the case of the sandbox issue I linked above, if you look at removeToast on the code below, the operations are being done on the list array.

Code with the issue:

export default function App() {
  const [list, setList] = useState([]);

  const removeToast = (id) => {
    const newList = list.filter(({ toastId }) => toastId !== id);
    setList([...newList]);
  };

  const addToast = () => {
    const toastId = Math.random().toString(36).substr(2, 9);
    const newList = [
      ...list,
      {
        toastId,
        content: (
          <>
            <button onClick={() => removeToast(toastId)}>Hi there</button>
            Hello {toastId}
          </>
        )
      }
    ];
    setList([...newList]);
  };

  return (
    <>
      <button onClick={addToast}>Show Toast</button>
      <Toaster removeToast={removeToast} toastList={list} />
    </>
  );
}

However since removeToast has a closure on list, we need to do the filtering on the previous state which is, again, accessible via the first parameter of the callback of setState

The fix:

const removeToast = (id) => {
  setList((prev) => {
    return prev.filter(({ toastId }) => toastId !== id);
  });
};

The Solution: https://codesandbox.io/s/suspicious-silence-r1n41?file=/src/App.js

You can pass event handler from container to child and then invoke event handler from client.

For example, let's say I have an app displaying list of items and each item have a delete button to remove them from the list. In this case, parent component will supply list of items and event handler to child component and then, child will be responsible for rendering and calling event handler.

Take a look at this codesandbox from which I am pasting following code:

import React, { useState } from "react";
import "./styles.css";

export function List(props) {
  return (
    <div>
      {props.items.map((i, idx) => (
        <div class="item" key={idx}>
          {i} <span onClick={() => props.onDelete(idx)}>X</span>
        </div>
      ))}
    </div>
  );
}

export default function App() {
  const [items, setItems] = useState([
    "Item 1",
    "Item 2",
    "Item 3",
    "Item 4",
    "Item 5",
    "Item 6"
  ]);

  const deleteItem = (index) => {
    if (index >= 0 && index < items.length) {
      const newItems = items.slice();
      newItems.splice(index, 1);
      setItems(newItems);
    }
  };

  return (
    <div className="App">
      <List items={items} onDelete={deleteItem}></List>
    </div>
  );
}

Related