删除特定索引的元素后反应组件不更新值
React component not updating values after element is deleted of specific index
我正在尝试创建一个带有删除按钮的购物清单,但是当我点击它时,其他项目被删除并且之前的值保留在屏幕上,即使第二次删除也是如此。请假设自定义按钮和图标已经是单独的包这是代码 -
import React, { useState } from 'react';
import { CButton, CDeleteButton } from '@custom/pkg';
const ShoppingList = () => {
const [state, setState] = useState(
[{ name: 'Apple', price: 60 }, { name: 'Banana', price: 20 }]
)
return (
<>
<h2>Product List</h2>
{state.map(({name, price}, idx) => (
<>
<div key={idx}>
<div>{name}</div>
<div>{price}</div>
<div>
<CButton id={idx} onClick={() => {
setState((prev) => [...prev.splice(idx, 1)])
}}>
<CDeleteButton/>
</CButton>
</div>
</div>
</>
))}
</>
)
}
export default ShoppingList;
该组件在模态组件中导入并渲染。如果我删除 "Apple"
,它仍然显示在屏幕上,而 "Banana"
被删除,反之亦然。
你应该展开prev数组然后使用splice
.
<CButton id={idx} onClick={()=>{setState((prev)=>[...prev].splice(idx,1))}}>
问题
问题是您使用的 React 密钥未关联到映射元素。当你插入或删除元素时,前面的元素都会改变位置,它们的键也会改变,但从 React 的角度来看,那个位置的键是相同的,所以 React 会放弃重新渲染。换句话说,当您在中间删除一个元素时,向上移动的元素将采用先前存在的键并且不会重新呈现。
这就是为什么在改变基础数据时映射数组时使用数组索引是一个糟糕的选择。
向每个要用作键的元素添加 GUID 或其他一些唯一 属性,或者使用属性组合生成一个。 React 键还需要位于最外层的映射元素上,在您的情况下为 Fragment
。由于片段在地图回调中不是必需的,您只返回一个节点,它可以被删除。
也不建议使用 .splice
,因为它会就地改变数组。如果你想从数组中删除一个元素,你应该使用 .slice
或 .filter
.
解决方案 1
使用添加的 id
属性.
const ShoppingList = () => {
const [state, setState] = useState([
{ id: 0, name: 'Apple', price: 60 }, // add id property
{ id: 1, name: 'Banana', price: 20 },
]);
const removeItem = id => () => {
setState(prev => prev.filter((el) => el.id !== id)); // filter by id
};
return (
<>
<h2>Product List</h2>
{state.map(({ id, name, price }) => (
<div key={id}> // id as key
<div>{name}</div>
<div>{price}</div>
<div>
<CButton id={id} onClick={removeItem(id)}> // pass id to handler
<CDeleteButton/>
</CButton>
</div>
</div>
))}
</>
)
}
解决方案 2
使用索引和从属性计算的 React 键。
const ShoppingList = () => {
const [state, setState] = useState([
{ name: 'Apple', price: 60 },
{ name: 'Banana', price: 20 },
]);
const removeItem = index => () => {
setState(prev => prev.filter((el, i) => i !== index)); // filter by index
};
return (
<>
<h2>Product List</h2>
{state.map(({ name, price }, idx) => (
<div key={`${name}-${price}`}> // computed key
<div>{name}</div>
<div>{price}</div>
<div>
<CButton id={idx} onClick={removeItem(idx)}> // pass index to handler
<CDeleteButton/>
</CButton>
</div>
</div>
))}
</>
)
}
我正在尝试创建一个带有删除按钮的购物清单,但是当我点击它时,其他项目被删除并且之前的值保留在屏幕上,即使第二次删除也是如此。请假设自定义按钮和图标已经是单独的包这是代码 -
import React, { useState } from 'react';
import { CButton, CDeleteButton } from '@custom/pkg';
const ShoppingList = () => {
const [state, setState] = useState(
[{ name: 'Apple', price: 60 }, { name: 'Banana', price: 20 }]
)
return (
<>
<h2>Product List</h2>
{state.map(({name, price}, idx) => (
<>
<div key={idx}>
<div>{name}</div>
<div>{price}</div>
<div>
<CButton id={idx} onClick={() => {
setState((prev) => [...prev.splice(idx, 1)])
}}>
<CDeleteButton/>
</CButton>
</div>
</div>
</>
))}
</>
)
}
export default ShoppingList;
该组件在模态组件中导入并渲染。如果我删除 "Apple"
,它仍然显示在屏幕上,而 "Banana"
被删除,反之亦然。
你应该展开prev数组然后使用splice
.
<CButton id={idx} onClick={()=>{setState((prev)=>[...prev].splice(idx,1))}}>
问题
问题是您使用的 React 密钥未关联到映射元素。当你插入或删除元素时,前面的元素都会改变位置,它们的键也会改变,但从 React 的角度来看,那个位置的键是相同的,所以 React 会放弃重新渲染。换句话说,当您在中间删除一个元素时,向上移动的元素将采用先前存在的键并且不会重新呈现。
这就是为什么在改变基础数据时映射数组时使用数组索引是一个糟糕的选择。
向每个要用作键的元素添加 GUID 或其他一些唯一 属性,或者使用属性组合生成一个。 React 键还需要位于最外层的映射元素上,在您的情况下为 Fragment
。由于片段在地图回调中不是必需的,您只返回一个节点,它可以被删除。
也不建议使用 .splice
,因为它会就地改变数组。如果你想从数组中删除一个元素,你应该使用 .slice
或 .filter
.
解决方案 1
使用添加的 id
属性.
const ShoppingList = () => {
const [state, setState] = useState([
{ id: 0, name: 'Apple', price: 60 }, // add id property
{ id: 1, name: 'Banana', price: 20 },
]);
const removeItem = id => () => {
setState(prev => prev.filter((el) => el.id !== id)); // filter by id
};
return (
<>
<h2>Product List</h2>
{state.map(({ id, name, price }) => (
<div key={id}> // id as key
<div>{name}</div>
<div>{price}</div>
<div>
<CButton id={id} onClick={removeItem(id)}> // pass id to handler
<CDeleteButton/>
</CButton>
</div>
</div>
))}
</>
)
}
解决方案 2
使用索引和从属性计算的 React 键。
const ShoppingList = () => {
const [state, setState] = useState([
{ name: 'Apple', price: 60 },
{ name: 'Banana', price: 20 },
]);
const removeItem = index => () => {
setState(prev => prev.filter((el, i) => i !== index)); // filter by index
};
return (
<>
<h2>Product List</h2>
{state.map(({ name, price }, idx) => (
<div key={`${name}-${price}`}> // computed key
<div>{name}</div>
<div>{price}</div>
<div>
<CButton id={idx} onClick={removeItem(idx)}> // pass index to handler
<CDeleteButton/>
</CButton>
</div>
</div>
))}
</>
)
}