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

如何在React Redux中用.filter()移除payload中的null对象/属性?

React Redux Todo应用:解决null对象报错与数据持久化问题

一、移除Todos数组中的null/无效对象

你的报错核心是数组中混入了null或无效对象,可从数据流入和渲染前过滤两个层面解决:

1. 在Reducer中拦截无效数据

修改todos.js的Reducer逻辑,确保所有操作都只保留有效Todo对象(非null、包含id和非空text):

import { ADD_TODO, RESET_TODO, DELETE_TODO } from "../actions/todoaction";

// 加载初始状态(后续会结合持久化)
const loadInitialState = () => ({
  todos: [
    { id: 1, text: "this is text" },
    { id: 2, text: "this is 2text" }
  ]
});

const initialtodoState = loadInitialState();

const todos = (state = initialtodoState, action) => {
  let newState;
  switch (action.type) {
    case ADD_TODO: {
      // 只接受非空text的Todo
      const isValid = action.payload?.text?.trim();
      if (!isValid) return state;
      newState = {
        ...state,
        todos: [...state.todos, action.payload].filter(todo => todo?.id && todo?.text?.trim())
      };
      break;
    }
    case RESET_TODO: {
      // 过滤数组中的null/空text项
      newState = {
        ...state,
        todos: state.todos.filter(todo => todo?.id && todo?.text?.trim())
      };
      break;
    }
    case DELETE_TODO: {
      newState = {
        ...state,
        todos: state.todos.filter(todo => todo.id !== action.payload)
      };
      break;
    }
    default:
      newState = state;
  }
  return newState;
};

export default todos;

2. 渲染前过滤无效项

在TodoList.js中,先过滤掉无效对象再渲染,避免null进入组件:

import { useSelector } from "react-redux";
import Todo from "./Todo";

const TodoList = () => {
  // 正确获取state中的todos数组
  const { todos } = useSelector(state => state);
  // 过滤掉null/无id/无text的项
  const validTodos = todos.filter(todo => todo?.id && todo?.text?.trim());

  return (
    <ul>
      {validTodos.length > 0
        ? validTodos.map(todo => (
            // 用todo.id作为key,而非整个对象(避免key不稳定)
            <Todo key={todo.id} todo={todo} id={todo.id} />
          ))
        : "no todos yet"}
    </ul>
  );
};

// 移除多余的connect和mapStateToProps(已用useSelector)
export default TodoList;

二、实现页面刷新后保留Todos数据

通过localStorage实现数据持久化,修改Reducer的初始化和更新逻辑:

1. 从localStorage加载初始状态

更新todos.js中的初始状态加载逻辑:

const loadInitialState = () => {
  try {
    const savedTodos = localStorage.getItem('todos');
    // 有缓存则用缓存,无则用默认数据
    return savedTodos ? JSON.parse(savedTodos) : {
      todos: [
        { id: 1, text: "this is text" },
        { id: 2, text: "this is 2text" }
      ]
    };
  } catch (e) {
    console.error('加载缓存失败:', e);
    return {
      todos: [
        { id: 1, text: "this is text" },
        { id: 2, text: "this is 2text" }
      ]
    };
  }
};

const initialtodoState = loadInitialState();

2. 状态更新时同步到localStorage

在Reducer每次返回新状态前,将数据存入localStorage:

const todos = (state = initialtodoState, action) => {
  let newState;
  // ...(之前的case逻辑不变)

  // 同步到localStorage
  localStorage.setItem('todos', JSON.stringify(newState));
  return newState;
};

三、修正代码中的其他潜在问题

1. 修复Todo组件的逻辑错误

移除多余的connect,补全编辑状态逻辑:

import { useState, useDispatch } from "react";
import { resetTodo, deleteTodo } from "../actions/actions";

const Todo = ({ todo, id }) => {
  const [editText, setEditText] = useState(todo.text);
  const [isEditing, setIsEditing] = useState(false);
  const dispatch = useDispatch();

  const taskContent = isEditing ? (
    <input
      value={editText}
      onChange={(e) => setEditText(e.target.value)}
      onBlur={() => setIsEditing(false)}
    />
  ) : (
    <>
      {todo.text}
      <button onClick={() => setIsEditing(true)}>edit</button>{" "}
      <button onClick={() => dispatch(resetTodo())}>reset</button>
    </>
  );

  return (
    <li style={{ listStyle: "none", display: "flex", marginTop: "20px" }}>
      {taskContent}
      <button
        onClick={() => dispatch(deleteTodo(id))}
        style={{ marginLeft: "30px", paddingTop: "0px" }}
      >
        delete
      </button>
    </li>
  );
};

export default Todo;

2. 修复AddTodo组件的addTodo调用

修正actions.js的action创建函数,以及AddTodo的输入逻辑:

// actions.js
import { ADD_TODO } from "../actions/todoaction";

let nextTodoId = 0;
export const addTodo = (text) => ({
  type: ADD_TODO,
  payload: {
    id: nextTodoId++,
    text: text.trim()
  }
});

export const resetTodo = () => ({ type: "RESET_TODO" });
export const deleteTodo = (id) => ({ type: "DELETE_TODO", payload: id });
// AddTodo.js
import { useState, useDispatch } from "react";
import { addTodo } from "../actions/actions";

const AddTodo = () => {
  const [text, setText] = useState("");
  const dispatch = useDispatch();

  const handleAdd = () => {
    if (text.trim()) {
      dispatch(addTodo(text));
      setText("");
    }
  };

  return (
    <div style={{ display: "flex", marginTop: "20px" }}>
      <input
        type="text"
        id="title"
        name="title"
        value={text}
        onChange={(e) => setText(e.target.value)}
        placeholder="Enter Title..."
      />
      <button onClick={handleAdd}>Add</button>
    </div>
  );
};

export default AddTodo;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 13:52:19