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

React Redux TodoList报错:多元素时点击保存触发TypeError

React Redux TodoList编辑功能报错排查

问题描述

使用React Redux开发待办事项列表时,当列表中待办项数量超过1个时,点击「保存修改」按钮会触发错误:TypeError: Cannot read properties of undefined (reading 'id'),且该错误仅在待办项数量大于1时出现。

相关代码

App.js

import React from "react";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
import AddTodo from "./components/AddTodo";
import TodoItem from "./components/TodoItem";
import { useSelector } from "react-redux";

function App() {
  const todos = useSelector((state) => state.todos);

  return (
    <div className="App">
      <AddTodo />
      <div className="todoList">
        {todos && todos.map((todo) => (
          <TodoItem key={todo.id} todo={todo} />
        ))}
      </div>
    </div>
  );
}

export default App;

Reducer(slice.js)

import { createSlice } from "@reduxjs/toolkit";

const todoSlice = createSlice({
  name: "todos",
  initialState: [
    {
      id: 1,
      item: "Example1",
      completed: false,
    },
    {
      id: 2,
      item: "Example2",
      completed: false,
    },
  ],
  reducers: {
    addTodo: (state, action) => {
      state.push(action.payload);
    },

    editTodo: (state, action) => {
      return state.map((todo) => {
        if (todo.id === action.payload.id) {
          return { ...todo, item: action.payload.item };
        }
      });
    },

    deleteTodo: (state, action) => {
      return state.filter((todo) => todo.id !== action.payload);
    },

    completeTodo: (state, action) => {
      return state.map((todo) => {
        if (todo.id === action.payload) {
          return { ...todo, completed: !todo.completed };
        }
        return todo;
      });
    },
  },
});

export const { addTodo, selectTodo, editTodo, deleteTodo, completeTodo } =
  todoSlice.actions;

export default todoSlice.reducer;

TodoItem.js

import React, { useState } from "react";
import { useDispatch } from "react-redux";
import { editTodo, deleteTodo, completeTodo, selectTodo } from "../store/slice";
import Button from "react-bootstrap/Button";
import Modal from "react-bootstrap/Modal";
import Form from "react-bootstrap/Form";

export default function TodoItem({ todo }) {
  const dispatch = useDispatch();
  const [showEdit, setShowEdit] = useState(false);
  const [editText, setEditText] = useState(todo.text);

  function handleDeleteTodo() {
    dispatch(deleteTodo(todo.id));
  };
  
  function handleCompleteTodo() {
    dispatch(completeTodo(todo.id));
  };

  function handleEditSave(){ 
      dispatch(editTodo({
        id: todo.id,
        item: editText,
      }));
      console.log(editText);
      setShowEdit(false);
    }
  
  return (
    <div>
      <h4
        style={{
          textDecoration: todo.completed ? "line-through" : "none",
          color: todo.completed ? "grey" : "black",
        }}
      >
        {todo?.item}
      </h4>
      <Button variant="success" onClick={handleDeleteTodo}>
        Delete
      </Button>

      <Button variant="success" onClick={handleCompleteTodo}>
        {todo?.completed ? "Incomplete" : "Complete"}
      </Button>

      <Button
        variant="primary"
        onClick={() => {
          setShowEdit(true);
        }}
      >
        Edit
      </Button>

      <Modal show={showEdit} onHide={() => setShowEdit(false)}>
        <Modal.Header closeButton>
          <Modal.Title>Edit</Modal.Title>
        </Modal.Header>
        <Modal.Body>
          <Form.Label> Edit To-do </Form.Label>
          <Form.Control
            type="text"
            value={editText ?? ''}
            placeholder={todo.item}
            onChange={(e) => setEditText(e.target.value)}
          />
        </Modal.Body>
        <Modal.Footer>
          <Button
            variant="primary"
            onClick={handleEditSave}
          >
            Save Changes
          </Button>
          <Button variant="secondary" onClick={() => setShowEdit(false)}>
            Close
          </Button>
        </Modal.Footer>
      </Modal>
    </div>
  );
}

AddTodo.js

import React, { useState } from "react";
import { useSelector, useDispatch } from "react-redux";
import { addTodo } from "../store/slice";

export default function AddTodo() {
  const [text, setText] = useState("");
  const todos = useSelector((state) => state.todos);
  const dispatch = useDispatch();

  function handleAddTodo(todo) {
    if (text === "") {
      alert("Input is Empty");
    } else {
      dispatch(
        addTodo({
          id: Date.now(),
          item: todo,
          completed: false,
        })
      );
      console.log({ todos });
      setText("");
    }
  }

  return (
    <div>
      <input
        type="text"
        value={text}
        onChange={(e) => setText(e.target.value)}
      />
      <button onClick={() => handleAddTodo(text)}>Add Todo</button>
    </div>
  );
}

错误原因及修复方案

1. Reducer中editTodo逻辑错误

在slice.js的editTodo reducer里,使用map循环时,仅当todo.id匹配时返回了新对象,不匹配的项没有返回值,导致map后的数组中出现undefined元素。当列表有多个项时,后续渲染TodoItem组件时,会尝试访问undefined的id属性,触发TypeError。

修复后的editTodo代码:

editTodo: (state, action) => {
  return state.map((todo) => {
    if (todo.id === action.payload.id) {
      return { ...todo, item: action.payload.item };
    }
    // 必须返回原todo,避免出现undefined
    return todo;
  });
},

2. TodoItem组件初始状态赋值错误

在TodoItem.js中,useState(todo.text)里的todo.text是错误的,因为你的todo对象中存储内容的属性是item不是text,导致初始editText为undefined。

修复后的初始状态代码:

const [editText, setEditText] = useState(todo.item);

总结

修复以上两处问题后,点击「保存修改」按钮就不会再触发错误,多待办项的编辑功能可以正常工作。

内容的提问来源于stack exchange,提问作者gjung

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:24:56