Я работаю с 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.
}
@Drew Reese «Вам действительно следует использовать обновление функционального состояния, например, setMeals(meals => food.filter(meal => food.props.keyProp !== key));» По сути, это то, что сработало для меня, но я не знаю, почему. Вы знаете?
Да, обычно это вызвано Замыканиями Javascript , когда обработчик обратного вызова removeRow, или, скорее, свойство deleteFunction не обновляется в компоненте Row или там, где он в конечном итоге приземляется и используется, не перехватывая или повторно не заключая обновленное meals состояние при обновлении. Вот почему я попросил более полный минимальный воспроизводимый пример, чтобы мы могли видеть полный путь от const [meals, setMeals] = useState([]) до места вызова обработчика. Имеет ли это смысл?



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Попробуйте, я уверен, что это решит проблему.
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, если функциональные методы массива можно вызывать непосредственно в строке. Что при этом достигается? @ДрюРиз
Языки программирования не знают о наших намерениях. Названия вещей дают нам власть над ними. Мы можем повторно использовать именованные функции в других частях нашей программы. По словам Джеральда Джея Сассмана, если у вас есть имя духа, вы имеете власть над ним.
Вам действительно следует использовать обновление функционального состояния, например.
setMeals(meals => meals.filter(meal => meal.props.keyProp !== key));, чтобы правильно ссылаться на текущее состояние, но я подозреваю, что у вас также есть странная проблема с закрытием Javascript. Можете ли вы отредактировать публикацию, чтобы поделиться полным минимальным воспроизводимым примером рендеринга кодаRowи каким быmealsсостоянием оно ни было?