You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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>
  );
}

问题原因分析

  1. 使用数组index作为组件key:删除中间项后,后续组件的index会发生变化,React会复用旧组件实例,导致子组件的本地state不会更新,依然保留之前的值。
  2. 子组件维护独立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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 22:25:07