React删除列表项时useRef获取id异常 始终删除最后一项
问题根源
useRef使用方式错误:单个useRef实例只能存储一个DOM节点引用,你把同一个mcqRef绑定到每道题的容器元素上,列表全部渲染完成后,mcqRef.current永远只会指向最后一个渲染的题目节点,不管点击哪个删除按钮,你拿到的都是最后一项的id,自然无法正确定位要删除的项。- id生成逻辑失效:你定义了
ids状态,但新增题目时从未更新该状态的值,所有新生成的题目id都是初始值1,id重复导致filter筛选逻辑完全失效。 - 违反React最佳实践:不要把
<McqQuestion />这类React元素直接存储在state中,state应该只保存渲染所需的纯数据,React元素要放在JSX渲染阶段生成。 - 列表渲染缺少唯一key:渲染题目列表时没有给每个列表项设置唯一的
key属性,React复用DOM节点时会出现节点错位,表现为你看到的id错乱、项未被正确删除的异常。 - HTML规范错误:所有删除按钮的label都设置了相同的
id="remove",同一个HTML文档中id必须全局唯一。
修复方案
完全不需要通过useRef读取DOM属性拿id,直接在渲染时把当前项的id传入删除回调即可,从根源上避免DOM操作带来的问题,完整修正代码如下:
- 修正题目新增逻辑,保证id唯一,state仅存储纯数据
const [nextId, setNextId] = useState(1); const [mcqAnswerList, setMcqAnswerList] = useState([]); const handleCreateMcqQuestion = () => { // 用函数式更新避免闭包拿到旧的state值 setMcqAnswerList(prev => [...prev, { id: nextId }]); // id自增,保证后续新增题目id全局唯一 setNextId(prev => prev + 1); };
- 修正删除逻辑,使用函数式更新避免闭包陷阱
const handleDeleteMcqQuestion = (targetId) => { setMcqAnswerList(prev => prev.filter(item => item.id !== targetId)); };
- 修正列表渲染逻辑,设置唯一key,直接传入当前项id,移除不必要的ref
{mcqAnswerList.map(questionItem => ( // 给每个列表项设置唯一key,避免DOM复用错位 <div key={questionItem.id}> <HStack spacing="24px" py={"9px"} data-id={questionItem.id}> <FormControl id={`type-${questionItem.id}`}> // 渲染时再生成React元素,不要存在state里 <McqQuestion /> </FormControl> </HStack> <div className="mx-[7px] w-[80px] cursor-pointer" // 直接传入当前项的id,不需要操作DOM拿值 onClick={() => handleDeleteMcqQuestion(questionItem.id)} > // 重复的DOM id改用class,符合HTML规范 <label className="remove-btn"> <Remove /> </label> </div> </div> ))}
提示:如果确实需要获取列表中多个项的DOM引用,应该使用ref回调函数存储ref数组,而不是用单个ref绑定所有项,但这个场景下完全不需要操作DOM,直接传参是最简单可靠的方案。
内容的提问来源于stack exchange,提问作者Ahmed Radi
相关产品推荐
相关产品推荐

