React Todo列表删除报错:Cannot read properties of undefined (reading 'id')
React Todo组件删除功能报错:Cannot read properties of undefined (reading 'id')
开发React组件Addtask实现Todo列表功能时,点击删除按钮(X图标)触发错误:Cannot read properties of undefined (reading 'id'),无法完成任务删除。相关代码及错误栈如下:
组件代码
import { type } from '@testing-library/user-event/dist/type'; import React, { useState } from 'react'; import './clint.css' export default function Addtask() { const [task, setTask] = useState([]); const [text, setText] = useState(''); const obj = [{ task: text.trim(), id: task.length }]; const handleSubmit = async (event) => { event.preventDefault(); try { setTask([...task, obj[obj.length-1].task]); const resp = await fetch( 'http://localhost:5000/addtask', { method: 'POST', body: JSON.stringify(task), headers: { 'content-type': 'application/json' } } ); if (resp.status === 200) { console.log('task added successfully'); } else { console.log("unable to post"); } } catch(error) { console.log("error"); } } function change(e) { const ip = e.target.value; setText(ip); } return ( <div> <h1>add user component</h1> <span>Add Task</span> <input onChange={change} placeholder='add a task' /> <button type='submit' onClick={handleSubmit}>submit</button> <ul> {task.map((tasks, i) => <li key={i}> {tasks} <button className='delete' onClick={() => { const removeItem = task.filter(() => { return obj[i].id !== i; }); setTask(removeItem); console.log(i) }} > <b>X</b> </button> </li> )} </ul> </div> ) }
错误信息
Cannot read properties of undefined (reading 'id') TypeError: Cannot read properties of undefined (reading 'id') at http://localhost:3000/main.158d69bbe90ec71b22a6.hot-update.js:94:29 at Array.filter (<anonymous>) at onClick (http://localhost:3000/main.158d69bbe90ec71b22a6.hot-update.js:93:37) at HTMLUnknownElement.callCallback (http://localhost:3000/static/js/bundle.js:29605:18) at Object.invokeGuardedCallbackDev (http://localhost:3000/static/js/bundle.js:29649:20) at invokeGuardedCallback (http://localhost:3000/static/js/bundle.js:29706:35) at invokeGuardedCallbackAndCatchFirstError (http://localhost:3000/static/js/bundle.js:29720:29) at executeDispatch (http://localhost:3000/static/js/bundle.js:33864:7) at processDispatchQueueItemsInOrder (http://localhost:3000/static/js/bundle.js:33890:11) at processDispatchQueue (http://localhost:3000/static/js/bundle.js:33901:9)
问题分析与修复
核心问题
obj的错误使用:obj是组件每次渲染时创建的仅含一个元素的数组,删除时访问obj[i],当列表项索引i>=1时,obj[i]必然是undefined,导致读取id报错。- 任务存储格式错误:当前
task数组仅存储任务字符串,没有保留唯一标识id,删除逻辑依赖索引和临时obj完全不成立。 - 提交逻辑错误:提交时把旧的
task数组传给后端,而非新添加的任务;且没有清空输入框。
修复后的代码
import React, { useState } from 'react'; import './clint.css' export default function Addtask() { // 存储带id的任务对象数组 const [tasks, setTasks] = useState([]); const [text, setText] = useState(''); const handleSubmit = async (event) => { event.preventDefault(); const trimmedText = text.trim(); if (!trimmedText) return; // 创建带唯一id的任务对象 const newTask = { id: Date.now(), // 用时间戳生成唯一id content: trimmedText }; try { // 更新本地状态 setTasks(prevTasks => [...prevTasks, newTask]); // 提交新任务给后端 const resp = await fetch( 'http://localhost:5000/addtask', { method: 'POST', body: JSON.stringify(newTask), headers: { 'content-type': 'application/json' } } ); if (resp.ok) { console.log('task added successfully'); // 清空输入框 setText(''); } else { console.log("unable to post"); // 提交失败时回滚状态 setTasks(prevTasks => prevTasks.filter(task => task.id !== newTask.id)); } } catch(error) { console.log("error:", error); // 报错时回滚状态 setTasks(prevTasks => prevTasks.filter(task => task.id !== newTask.id)); } } const handleDelete = (taskId) => { // 根据id过滤删除对应任务 setTasks(prevTasks => prevTasks.filter(task => task.id !== taskId)); // 可选:调用后端删除接口 // fetch(`http://localhost:5000/deletetask/${taskId}`, { method: 'DELETE' }) // .catch(err => console.log("delete error:", err)); } function change(e) { setText(e.target.value); } return ( <div> <h1>add user component</h1> <span>Add Task</span> <input onChange={change} value={text} placeholder='add a task' /> <button type='submit' onClick={handleSubmit}>submit</button> <ul> {tasks.map((task) => <li key={task.id}> {task.content} <button className='delete' onClick={() => handleDelete(task.id)} > <b>X</b> </button> </li> )} </ul> </div> ) }
关键修复点
- 将
task数组改为存储包含id和content的对象,确保每个任务有唯一标识。 - 删除逻辑通过任务的
id进行过滤,不再依赖索引和临时变量obj。 - 提交时使用函数式更新状态,避免闭包捕获旧状态的问题。
- 添加输入框内容清空逻辑,优化用户体验。
- 提交失败/报错时添加状态回滚,保证本地数据一致性。
内容的提问来源于stack exchange,提问作者rohit
相关产品推荐
相关产品推荐

