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

React useReducer删待办触发undefined的map读取报错

问题描述

在React项目中使用useReducer hook实现Todo待办列表的新增、删除功能时,测试删除逻辑出现异常。

  • 报错信息:TypeError: Cannot read properties of undefined (reading 'map')
  • 复现规律:
    • 将reducer中deleteItem类型action对应的返回逻辑修改为和addItem分支一致的结构时,功能可正常运行
    • 如果修改为其他逻辑(例如仅执行console.log、或直接返回数组过滤结果),就会触发上述报错
相关代码

App组件代码

import React, { useState, useReducer } from "react";
import { FaBeer } from "react-icons/fa";

import ToDoItem from "./components/ToDoItem";
import "./App.css";

function App() {
  const [inputText, setInputText] = useState("");
  const [state, dispatch] = useReducer(reducer, [], init);

  function init(initArray) {
    return { todos: initArray };
  }

  function reducer(state, action) {
    switch (action.type) {
      case "addItem":
        return { todos: [...state.todos, action.payload.inputText] };
      case "deleteItem":
         return state.todos.filter((item, index) => {
                return index !== action.payload.id;
              });
        //  return { todos: [...state.todos, action.payload.id] };
            
      default:
        return state.todos;
    }
  } 


  function handleChange(event) {
    const newValue = event.target.value;
    setInputText(newValue);
  }
  
  function addItem(e) {
    e.preventDefault();
    dispatch({ type: "addItem", payload: { inputText: inputText } });
    setInputText("");
  }



  return (
    <div className="container">
      <div className="heading">
        <h1>To-Do List</h1>
      </div>
      <div className="form">
        <form onSubmit={addItem}>
          <input onChange={handleChange} type="text" value={inputText} />
          <button type="submit">
            <span>Add</span>
          </button>
        </form>
      </div>
      <div>
        <ul>
          {state.todos.map((eachItem, index) => (
            <ToDoItem
              key={index}
              id={index}
              text={eachItem}          
              dispatch={dispatch}
            />
          ))}
        </ul>
      </div>
    </div>
  );
}

export default App;

ToDoItem组件代码

import React from "react";

import audio from './clickSound.wav';
import CheckBox from "./CheckBox";
import Delete from "./Delete";

function ToDoItem(props) {
  const [clicked, setClicked] = React.useState(false);

  function cross() {
    setClicked((prevValue) => {
      return !prevValue;
    });
  }

  function playSound(){
    const sound = new Audio(audio)
    sound.play()
  }

  
  return (
    <div>
      <CheckBox />
      <label>
        <input type="checkbox" className="normalCheckbox" onClick={()=>{cross();playSound();}} />
        <li style={{ textDecoration: clicked ? "line-through" : "none" }}>
          {props.text}
        </li>
      </label>

      <Delete
        onClick={() =>
          props.dispatch({ type: "deleteItem", payload: { id: props.id } })
        }
      />
    </div>
  );
}

export default ToDoItem;

Delete组件代码

import React from 'react';

import { AiFillDelete} from "react-icons/ai";


const Delete = props => {
  return <button onClick={props.onClick}type="button"> <AiFillDelete/> </button>
  
};

export default Delete;
问题根源

报错的核心原因是reducer返回的状态结构和初始状态不一致:

  1. 初始化时通过init函数返回的state结构为{ todos: 待办数组 },addItem分支也遵守了这个结构,返回带todos属性的对象
  2. deleteItem分支直接返回了filter后的纯数组,导致state从对象变成数组类型,后续渲染读取state.todos时,数组本身没有todos属性,拿到的值是undefined,调用map方法就会抛出类型错误
  3. 如果deleteItem分支只写console.log不写返回值,函数默认返回undefined,state会被更新为undefined,读取state.todos同样会报错
  4. 之前测试时把deleteItem分支改成和addItem一致的返回结构(即返回带todos属性的对象)时不报错,本质是状态结构保持了统一,只是删除逻辑本身不正确而已。另外default分支直接返回state.todos也存在同样的结构错误问题。
修复方案

修改reducer逻辑,确保所有分支始终返回结构统一的状态对象:

function reducer(state, action) {
  switch (action.type) {
    case "addItem":
      return { todos: [...state.todos, action.payload.inputText] };
    case "deleteItem":
      // 包裹在对象中返回,保持state结构一致
      return {
        todos: state.todos.filter((item, index) => index !== action.payload.id)
      };
    default:
      // 默认返回完整原状态,不要返回state.todos
      return state;
  }
}

注意:useReducer的reducer是纯函数,必须始终返回与初始状态结构完全一致的新状态,禁止随意修改状态的数据类型,也不能省略返回值导致返回undefined。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 21:57:22