Компонент имеет доступ только к старому состоянию массива

Я работаю с React, и у меня есть таблица, в которую пользователи могут добавлять и удалять строки. Стол состоит из множества компонентов. Каждый компонент/строка имеет значок корзины, при нажатии на который выполняется функция удаления строки. Казалось бы, каждый компонент имеет доступ только к старому состоянию массива/таблицы, поэтому удаление не работает корректно. Есть идеи, как я могу решить эту проблему?

Пример компонента массива useState, составляющего эту таблицу:

<Row
  className = {className}
  columns = {columns}
  text = {text}
  key = {key}
  keyProp = {key}
  deleteFunction = {() => removeRow(key)}
/>

Функция удаления, которая является опорой для каждой строки/компонента массива:

function removeRow(key) {
  setMeals(meals.filter(i => i.props.keyProp !== key));
  // This should only remove the one row which had its trash can icon clicked.
  // If there's 10 rows and you click the trash can for row 4, rows 4-10
  // will be deleted.
  // If you click the trash can for row 7, rows 7-10 will be deleted and so on.
}

Вам действительно следует использовать обновление функционального состояния, например. setMeals(meals => meals.filter(meal => meal.props.keyProp !== key));, чтобы правильно ссылаться на текущее состояние, но я подозреваю, что у вас также есть странная проблема с закрытием Javascript. Можете ли вы отредактировать публикацию, чтобы поделиться полным минимальным воспроизводимым примером рендеринга кода Row и каким бы meals состоянием оно ни было?

Drew Reese 02.05.2024 02:04

@Drew Reese «Вам действительно следует использовать обновление функционального состояния, например, setMeals(meals => food.filter(meal => food.props.keyProp !== key));» По сути, это то, что сработало для меня, но я не знаю, почему. Вы знаете?

user24513854 03.05.2024 20:47

Да, обычно это вызвано Замыканиями Javascript , когда обработчик обратного вызова removeRow, или, скорее, свойство deleteFunction не обновляется в компоненте Row или там, где он в конечном итоге приземляется и используется, не перехватывая или повторно не заключая обновленное meals состояние при обновлении. Вот почему я попросил более полный минимальный воспроизводимый пример, чтобы мы могли видеть полный путь от const [meals, setMeals] = useState([]) до места вызова обработчика. Имеет ли это смысл?

Drew Reese 03.05.2024 20:54
Поведение ключевого слова "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) для оценки ваших знаний,...
1
3
146
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

Попробуйте, я уверен, что это решит проблему.

function removeRow(key) {
// This updates the previous meal array whenever there's a removal in the meal array
 setMeals((prevMeals) => {
    return prevMeals.filter((meal) => meal.props.keyProp !== key)
})

Также улучшите функцию удаления, как показано ниже:

<Row className = {className} columns = {columns} text = {text} key = {key} keyProp = {key} deleteFunction = {() => removeRow.bind(null, key)} />

Как отмечали другие, ваша проблема, скорее всего, находится в другом месте вашего кода. Использование функционального обновления предпочтительнее, но я не думаю, что filter — правильный выбор для удаления известного индекса из массива. Вот работающая демо-версия, с которой вы можете работать:

function App() {
  const [rows, setRows] = React.useState(Array.from("ABCDEF"))
  
  const removeRow = index => event => {
    setRows(prev => prev.slice(0, index).concat(prev.slice(index + 1)))
  }

  return rows.map((key, index) =>
    <div key = {key} onClick = {removeRow(index)} children = {key} />
  )
}

ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
div ~ div { margin-top: 0.5rem; }
div > div { cursor: pointer; border: 1px solid silver; padding: 0.5rem }
<script crossorigin src = "https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src = "https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id = "app"></div>

Это обычная необходимость неизменяемого удаления элемента из массива. Вероятно, вам будет полезно отделить эту логику от вашего компонента, чтобы ее можно было повторно использовать в других частях вашей программы.

function removeAt(arr, index) {
  return arr.slice(0, index).concat(arr.slice(index + 1))
}


function App() {
  const [rows, setRows] = React.useState(Array.from("ABCDEF"))
  
  const removeRow = index => event => {
    setRows(prev => removeAt(prev, index))
  }

  return rows.map((key, index) =>
    <div key = {key} onClick = {removeRow(index)} children = {key} />
  )
}

ReactDOM.createRoot(document.querySelector("#app")).render(<App />)
div ~ div { margin-top: 0.5rem; }
div > div { cursor: pointer; border: 1px solid silver; padding: 0.5rem }
<script crossorigin src = "https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src = "https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<div id = "app"></div>

Использование Array.filter — это стандартный метод удаления элементов из массива и возврата новой ссылки на массив для React. Я не знаю, зачем кому-то использовать два Array.slice звонка и один Array.concat звонок вместо одного .filter. @ДрюРиз

Давайте зададим еще один вопрос: зачем кому-то использовать целую .filter вместо двух .slice и одной .concat?

Или, говоря по-нашему, зачем кому-то сканировать весь массив, если позиция целевого элемента уже известна? 🤷🏽 🤷🏽‍♀️

программа размер массива сравнения вызовы функций filter 100 100 100 filter 1000 1000 1000 filter 10 000 10 000 10 000 -- -- -- -- removeAt 100 0 3 removeAt 1000 0 3 removeAt 10 000 0 3

Тест показывает removeAt производительность примерно в 5 раз быстрее, чем filter для размеров массива N=[100..10000] -

программа размер массива операции/с +/- filter 100 289 000 на 64% медленнее removeAt 100 804 000 На 278 % быстрее -- -- -- -- filter 1000 22 000 на 83% медленнее removeAt 1000 129 000 На 586 % быстрее -- -- -- -- filter 10 000 2100 на 87% медленнее removeAt 10 000 16 000 на 761% быстрее

Также нет смысла абстрагировать утилиту removeAt, если функциональные методы массива можно вызывать непосредственно в строке. Что при этом достигается? @ДрюРиз

Языки программирования не знают о наших намерениях. Названия вещей дают нам власть над ними. Мы можем повторно использовать именованные функции в других частях нашей программы. По словам Джеральда Джея Сассмана, если у вас есть имя духа, вы имеете власть над ним.

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

При запуске приложения реагирования с Vite файлы .ts считываются из папки сервера
Нет дескриптора запроса POST веб-перехватчика, несмотря на получение запроса OK POST от Stripe в CLI — NextJS 14
Как обращаться (а не игнорировать) реагировать на правило линтера исчерпывающего разрешения, не вызывая бесконечный цикл повторного рендеринга при получении данных
Вывод данных в таблицу
Недопустимое значение 0 передано в checkMaxIfStatementsInShader при попытке использовать холст через перехватчик React useRef
Таблицы стилей реакции-native-markdown-display
Почему мое второе значение OnClick Isloading не работает в запросе на мутацию RTK Query?
ReactJS: переход на новую страницу со страницы автоматического обновления не работает
Получение «TypeError: невозможно прочитать свойства null (чтение «get»)» при использовании useSearchParams в файле Storybook
Как раскрыть «параметр», как это делает React в onChange, onClick и т. д.?