我遇到了以下问题:我使用 React 和 Redux 创建了一个待办事项应用程序,它运行良好,直到我决定实现 localStorage。当我启动我的应用程序时,我收到一个非常常见的错误:“未捕获错误:对象作为 React 子项无效(找到:带有键 {id、title、isCompleted、isEditing、number} 的对象)”。 如果您打算渲染子集合,请改用数组。但是,我不知道需要在哪里进行更改以防止出现此错误。我创建了一个减速器:
import {
ADD_TASK,
REMOVE_TASK,
UPDATE_TASK,
} from "../actionsTypes";
import { v4 as uuidv4 } from "uuid";
export const initialState = {
tasklist: [], //tasklist
};
export function taskReducer(state = initialState, action) {
switch (action.type) {
case ADD_TASK:
return {
...state,
tasklist: [
...state.tasklist,
{
id: uuidv4(),
title: action.payload,
isCompleted: false,
isEditing: false,
number: state.tasklist.length + 1,
},
],
};
case REMOVE_TASK:
return {
...state,
tasklist: state.tasklist.filter((todo) => action.payload !== todo.id),
};
case UPDATE_TASK:
return {
...state,
tasklist: state.tasklist.map((prevTask) =>
prevTask.id === action.payload.id
? { ...prevTask, title: action.payload.newTitle }
: prevTask
),
};
default:
return state;
}
}
我还有我的 TaskList.jsx,我尝试在其中插入和从 LocalStorage 中提取数据:
import React, { useState, useEffect } from "react";
import InputTask from "../InputTask/InputTask";
import HeaderOfTaskList from "../HeaderOfTaskList/HeaderOfTaskList";
import Task from "../Task/Task";
import { useDispatch, useSelector } from "react-redux";
import { removeTask, addTask } from "../../store/actions";
import InputSearch from "../InputSearch/InputSearch";
import useInput from "../../hooks/useInput";
import "./TaskList.css";
export const TaskList = () => {
const dispatch = useDispatch();
const tasks = useSelector((state) => state.tasklist);
const input = useInput();
const [searchTerm, setSearchTerm] = useState("");
useEffect(() => {
const storedTasks = localStorage.getItem("tasks"); //JSON.stringify(localStorage.getItem("tasks"));
if (storedTasks) {
dispatch(addTask(JSON.parse(storedTasks)));
}
}, [dispatch, tasks]);
useEffect(() => {
localStorage.setItem("tasks", JSON.stringify(tasks));
}, [tasks]);
const filteredTasks = tasks.filter((task) =>
typeof task.title === "string" &&
task.title.toLowerCase().includes(searchTerm.toLowerCase())
);
const isTaskListEmpty = filteredTasks.length === 0;
const handleInputChange = (value) => {
setSearchTerm(value);
};
const handleDelete = (id) => {
dispatch(removeTask(id));
};
const handleAddTask = (tasklist) => {
dispatch(addTask(tasklist));
};
return (
<div>
<InputTask addTask={handleAddTask} />
<InputSearch onInputChange={handleInputChange} />
<HeaderOfTaskList />
{!isTaskListEmpty ? (
<ul>
{filteredTasks
.filter((task) =>
task.title.toLowerCase().includes(input.value.toLowerCase())
)
.map((task) => (
<Task
task={task}
key={task.id}
onDelete={() => handleDelete(task.id)}
/>
))}
</ul>
) : (
<ul>
{tasks.map((task) => (
<Task
task={task}
key={task.id}
onDelete={() => handleDelete(task.id)}
/>
))}
</ul>
)}
</div>
);
};
我的任务组件负责渲染任务列表:
import React, { useState } from "react";
import { useDispatch } from "react-redux";
import Button from "@mui/material/Button";
import DeleteIcon from "@mui/icons-material/Delete";
import EditIcon from "@mui/icons-material/Edit";
import { Checkbox } from "@mui/material";
import { updateTask } from "../../store/actions";
import "./Task.css";
const Task = ({ task, onDelete }) => {
const [isEditing, setIsEditing] = useState(false);
const [inputValue, setInputValue] = useState(task.title);
const [theme, setTheme] = useState("dark");
const dispatch = useDispatch();
const onEdit = () => {
setIsEditing(!isEditing);
};
const onCheckBoxClicked = () => {
if (!task.isCompleted) {
setTheme(theme === "light" ? "dark" : "light");
}
};
const onSaveClicked = () => {
dispatch(updateTask(task.id, inputValue));
setIsEditing(!isEditing);
};
return (
<li className={theme}>
{isEditing ? (
<input
id="edittask"
type="text"
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
}}
onKeyDown={(e) => {
if (e.key === "Enter" && isEditing) {
onSaveClicked();
}
}}
/>
) : (
<>
{task.number}
<p>{task.title}</p>
<Checkbox onClick={onCheckBoxClicked} />
</>
)}
{!isEditing ? (
<Button onClick={onEdit} variant="outlined" endIcon={<EditIcon />}>
Edit
</Button>
) : (
<Button onClick={onSaveClicked} variant="outlined">
Save
</Button>
)}
<Button onClick={onDelete} variant="outlined" endIcon={<DeleteIcon />}>
Remove
</Button>
</li>
);
};
export default Task;
import React, { useState } from "react";
import "./InputTask.css";
import Button from "@mui/material/Button";
const InputTask = ({ addTask }) => {
const [value, setValue] = useState("");
const handleChange = () => {
if (value) {
addTask(value);
}
setValue("");
};
const isEnterButtonClicked = (e) => {
if (e.key === "Enter") {
addTask(value);
setValue("");
}
};
return (
<div className="sectioninput">
<input
value={value}
type="text"
id="typetask"
placeholder="What are you gonna do?"
onChange={(e) => setValue(e.target.value)}
onKeyDown={isEnterButtonClicked}
/>
<Button variant="outlined" onClick={handleChange}>
Add Task
</Button>
</div>
);
};
export default InputTask;
导入{ 添加任务, 删除_任务, 更新任务, 完成_任务, } 来自“./actionsTypes”;
导出 const addTask = (标题) => ({ 类型:ADD_TASK, 有效负载:标题, });
导出常量removeTask =(id)=>({ 类型:REMOVE_TASK, 有效负载:ID, });
导出 const updateTask = (id, 标题) => ({ 类型:更新任务, 有效负载:{id,newTitle:标题}, });
导出 const CompleteTask = (id) => ({ 类型:COMPLETE_TASK, 有效负载:ID, });
enter code here
创建 localstorage 的自定义钩子,或者我个人更喜欢 localforage,然后是 localstorage。 LocalForage 使用异步存储,例如 IndexedDB 或 WebSQL,具有与 localStorage 类似的简单 API。