React中MUI ClickAwayListener在map渲染多下拉时点击外部失效问题
问题原因
- 下拉菜单默认挂载到根节点:MUI的Select组件的下拉菜单(Menu)默认开启了
portal属性,会被渲染到body根节点下,不在ClickAwayListener包裹的FormControl的DOM子树范围内,因此点击下拉菜单本身、下拉选项都会被ClickAwayListener判定为「点击外部」,触发回调。 - 循环渲染的标识冲突:你用map渲染了3个相同的Select,所有Select的
id、labelId属性完全重复,且所有Select共享同一个personName状态,DOM节点识别冲突,进一步导致ClickAwayListener的事件判定异常,这也是单个渲染时功能正常、循环渲染就出问题的核心原因。
解决方案
步骤1:关闭下拉菜单的portal特性
给Select的MenuProps新增disablePortal: true配置,让下拉菜单渲染在Select的父级DOM节点内,属于ClickAwayListener的监听范围,点击下拉就不会触发外部点击回调。
步骤2:给循环渲染的每个Select分配唯一标识和独立状态
将选中值状态改为数组,每个下标对应一个Select的选中结果,同时给id、labelId等属性拼接循环的index值,保证所有DOM标识唯一。
修正后代码示例
export default function MultipleSelectChip() { const theme = useTheme(); // 每个下拉对应独立的选中状态 const [personNameList, setPersonNameList] = React.useState([[], [], []]); return ( <div> {/* 循环时携带index做唯一标识 */} {[1, 2, 3].map((_, index) => { // 每个下拉对应独立的修改逻辑 const handleChange = (event) => { const { target: { value } } = event; const newList = [...personNameList]; newList[index] = typeof value === "string" ? value.split(",") : value; setPersonNameList(newList); }; return ( <div key={index}> <ClickAwayListener onClickAway={() => console.log(`第${index+1}个下拉点击外部`)}> <FormControl sx={{ m: 1, width: 300 }}> {/* 所有标识拼接index保证唯一 */} <InputLabel id={`demo-multiple-chip-label-${index}`}>Chip</InputLabel> <Select labelId={`demo-multiple-chip-label-${index}`} id={`demo-multiple-chip-${index}`} multiple value={personNameList[index]} onChange={handleChange} input={ <OutlinedInput id={`select-multiple-chip-${index}`} label="Chip" /> } renderValue={(selected) => ( <Box sx={{ display: "flex", flexWrap: "wrap", gap: 0.5 }}> {selected.map((value) => ( <Chip key={value} label={value} /> ))} </Box> )} {/* 新增disablePortal关闭portal挂载 */} MenuProps={{...MenuProps, disablePortal: true}} > {names.map((name) => ( <MenuItem key={name} value={name} style={getStyles(name, personNameList[index], theme)} > {name} </MenuItem> ))} </Select> </FormControl> </ClickAwayListener> </div> ); })} </div> ); }
内容的提问来源于stack exchange,提问作者Asking
相关产品推荐
相关产品推荐

