如何在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
相关产品推荐
相关产品推荐

