MUI Select组件结合数组使用时删除操作渲染异常问题
问题:MUI Select列表删除后渲染异常
我尝试在列表中使用MUI Select组件,基于数据渲染多个Select,同时支持添加和删除单个Select。但删除操作后出现异常:控制台显示数据已正确删除,页面渲染的Select值却未更新。比如添加3个Select并分别设置值为1、2、3,删除第2项后,页面仍显示两个Select的值为1和2,而非预期的1和3。
相关代码
父组件代码
function SelectTest({}: Props) { const [selectArray, setSelectArray] = useState([{name: "Select 1", value: 1},{name: "Select 2", value: 2},{name: "Select 3", value: 3}]) const handleAddSelect = () => { setSelectArray((prev) => { return [{name: "New select", value: 1}, ...prev]; }) } const handleDeleteSelect = (id: number) => { let temp = [...selectArray]; console.log(id) temp.splice(id, 1); console.log(temp) setSelectArray(temp) } return ( <Container sx={{ display: "flex", flexDirection: "column", gap: 1, }} > <List> {selectArray.map((item, index) => ( <SelectCard id={index} key={index} onDelete={handleDeleteSelect} value={item.value}/> ))} </List> <Button onClick={handleAddSelect}>Add Select</Button> </Container> ) } export default SelectTest
子组件代码
type Props = { onDelete: Function, id: number, value: number }; const values = [ { label: "Item 1", value: 1, }, { label: "Item 2", value: 2, }, { label: "Item 3", value: 3, }, { label: "Item 4", value: 4, }, ]; export default function SelectCard(props: Props) { const [item, setItem] = useState<number>(props.value) const handleItemChange = (event: SelectChangeEvent<typeof item>) => { const { target: { value }, } = event; console.log(value); setItem(value as number); } return ( <Card> <CardHeader title={"Select item " + props.id} sx={{ backgroundColor: colorConfigs.sidebar.bg, color: colorConfigs.sidebar.color, display: "flex", justifyContent: "space-evenly", }} action={ <IconButton onClick={() => { props.onDelete(props.id); }} sx={{ color: colorConfigs.sidebar.color, alignSelf: "flex-end", }} > <DeleteOutline /> </IconButton> } /> <CardContent sx={{ display: "flex", flexDirection: "row", gap: 1, pt: 2, }} > <FormControl sx={{ flex: 1, }} > <InputLabel>{props.id}</InputLabel> <Select value={item} onChange={handleItemChange} input={ <OutlinedInput label="Item" /> } > {values.map((item, index) => ( <MenuItem key={index} value={item.value}> {item.label} </MenuItem> ))} </Select> </FormControl> </CardContent> </Card> ); }
问题原因分析
- 使用数组index作为组件key:删除中间项后,后续组件的index会发生变化,React会复用旧组件实例,导致子组件的本地state不会更新,依然保留之前的值。
- 子组件维护独立state:子组件用本地state保存选中值,没有和父组件的props同步。当父组件数据更新后,子组件不会自动更新自身的state,造成页面显示和数据不一致。
解决方案
步骤1:替换index为唯一标识作为key
给每个Select项添加唯一id,避免用index作为key,确保React能正确识别组件实例:
修改父组件的初始数据、添加和删除逻辑:
function SelectTest({}: Props) { // 给每个项添加唯一id const [selectArray, setSelectArray] = useState([ {id: 1, name: "Select 1", value: 1}, {id: 2, name: "Select 2", value: 2}, {id: 3, name: "Select 3", value: 3} ]) const handleAddSelect = () => { setSelectArray((prev) => { // 生成新的唯一id const newId = prev.length > 0 ? Math.max(...prev.map(item => item.id)) + 1 : 1; return [{id: newId, name: "New select", value: 1}, ...prev]; }) } // 根据id删除项,而非index const handleDeleteSelect = (id: number) => { setSelectArray(prev => prev.filter(item => item.id !== id)) } return ( <Container sx={{ display: "flex", flexDirection: "column", gap: 1, }} > <List> {selectArray.map((item) => ( // 使用item.id作为key和传递给子组件的id <SelectCard id={item.id} key={item.id} onDelete={handleDeleteSelect} value={item.value}/> ))} </List> <Button onClick={handleAddSelect}>Add Select</Button> </Container> ) }
步骤2:改为受控组件统一管理状态
让父组件统一管理所有Select的选中值,子组件不再维护本地state,确保数据和视图同步:
修改子组件
// 更新Props类型,增加值变化回调 type Props = { onDelete: (id: number) => void, id: number, value: number, onValueChange: (id: number, value: number) => void }; const values = [ {label: "Item 1", value: 1}, {label: "Item 2", value: 2}, {label: "Item 3", value: 3}, {label: "Item 4", value: 4}, ]; export default function SelectCard(props: Props) { const handleItemChange = (event: SelectChangeEvent<number>) => { const { target: { value } } = event; // 通知父组件更新对应项的值 props.onValueChange(props.id, value as number); } return ( <Card> <CardHeader title={"Select item " + props.id} sx={{ backgroundColor: colorConfigs.sidebar.bg, color: colorConfigs.sidebar.color, display: "flex", justifyContent: "space-evenly", }} action={ <IconButton onClick={() => props.onDelete(props.id)} sx={{ color: colorConfigs.sidebar.color, alignSelf: "flex-end", }} > <DeleteOutline /> </IconButton> } /> <CardContent sx={{ display: "flex", flexDirection: "row", gap: 1, pt: 2, }} > <FormControl sx={{flex: 1}}> <InputLabel>{props.id}</InputLabel> <Select value={props.value} // 直接使用父组件传递的value onChange={handleItemChange} input={<OutlinedInput label="Item" />} > {values.map((item) => ( <MenuItem key={item.value} value={item.value}> {item.label} </MenuItem> ))} </Select> </FormControl> </CardContent> </Card> ); }
修改父组件,添加值变化处理函数
function SelectTest({}: Props) { const [selectArray, setSelectArray] = useState([ {id: 1, name: "Select 1", value: 1}, {id: 2, name: "Select 2", value: 2}, {id: 3, name: "Select 3", value: 3} ]) const handleAddSelect = () => { setSelectArray((prev) => { const newId = prev.length > 0 ? Math.max(...prev.map(item => item.id)) + 1 : 1; return [{id: newId, name: "New select", value: 1}, ...prev]; }) } const handleDeleteSelect = (id: number) => { setSelectArray(prev => prev.filter(item => item.id !== id)) } // 处理子组件的值变化,更新对应项的value const handleValueChange = (id: number, newValue: number) => { setSelectArray(prev => prev.map(item => item.id === id ? {...item, value: newValue} : item )) } return ( <Container sx={{ display: "flex", flexDirection: "column", gap: 1, }} > <List> {selectArray.map((item) => ( <SelectCard id={item.id} key={item.id} onDelete={handleDeleteSelect} value={item.value} onValueChange={handleValueChange} // 传递值变化回调 /> ))} </List> <Button onClick={handleAddSelect}>Add Select</Button> </Container> ) }
可选方案:子组件监听props更新
如果需要子组件临时维护state,可以通过useEffect监听props变化来更新本地state,但不推荐这种方式,容易造成数据不一致:
export default function SelectCard(props: Props) { const [item, setItem] = useState<number>(props.value) // 监听props.value变化,更新本地state useEffect(() => { setItem(props.value) }, [props.value]) const handleItemChange = (event: SelectChangeEvent<typeof item>) => { const { target: { value } } = event; setItem(value as number); } // ... 其余代码不变 }
内容的提问来源于stack exchange,提问作者Angryjames
相关产品推荐
相关产品推荐

