我有一个任务组件,里面有一个handleCheckboxChange,如果我在3秒后检查tast,它应该进入完成列表,如果取消选中,它应该进入待办事项列表的开头,它工作正常,但是当我检查或取消检查时不止一项任务它只是改变了最后一个位置我应该如何解决这个问题? 我的身体成分:
import React, { useEffect, useState } from 'react'
import TodoContainer from './TodoContainer'
export default function Body() {
const [todos, setTodos] = useState([]);
const handleDeleteTask = (taskIndex, section) => {
const updatedTodos = { ...todos };
updatedTodos[section] = todos[section].filter((_, index) => index !== taskIndex);
localStorage.setItem("todos", JSON.stringify(updatedTodos));
setTodos(updatedTodos);
};
const handleAddTask = (newTask, section) => {
const updatedTodos = { ...todos };
updatedTodos[section] = [newTask, ...updatedTodos[section]];
localStorage.setItem("todos", JSON.stringify(updatedTodos));
setTodos(updatedTodos);
};
useEffect(() => {
const hasInitialDataLS = localStorage.getItem("hasInitialDataLS");
if (!hasInitialDataLS) {
// Pre-save initial information
const preSavedData = {
todo: [
{ title: 'Start with meditation, exercise & breakfast for a productive day', check: 0 },
{ title: 'Read to learn something new every day', check: 0 },
{ title: 'Learn something fresh & relevant', check: 0 }
],
doing: [
{ title: 'Engage & question in meetings', check: 0 },
{ title: 'Use time-blocking for effective days', check: 0 }
],
done: [
{ title: 'Use time-blocking for effective days', check: 1 },
{ title: 'Congratulate yourself for incorporating healthier habits into your lifestyle, like regular exercise or mindful eating', check: 1 }
]
};
localStorage.setItem("todos", JSON.stringify(preSavedData));
localStorage.setItem("hasInitialDataLS", true);
setTodos(preSavedData);
}
if (hasInitialDataLS) {
const storeTodos = localStorage.getItem("todos");
if (storeTodos) {
setTodos(JSON.parse(storeTodos));
}
}
}, []);
useEffect(() => {
// Update localStorage whenever todos state changes
localStorage.setItem("todos", JSON.stringify(todos));
}, [todos]); // This effect runs whenever todos change
return (
<div className="body">
<TodoContainer
className="todo"
title="Todo"
requirementTasks={todos.todo}
onDeleteTask={(taskIndex) => handleDeleteTask(taskIndex, 'todo')}
setTodos={setTodos}
todos={todos}
onAddTask={(newTask) => handleAddTask(newTask, 'todo')}
/>
<TodoContainer
className="doing"
title="Doing 💪"
requirementTasks={todos.doing}
onDeleteTask={(taskIndex) => handleDeleteTask(taskIndex, 'doing')}
setTodos={setTodos}
todos={todos}
onAddTask={(newTask) => handleAddTask(newTask, 'doing')}
/>
<TodoContainer
className="done"
title="Done 🎉"
requirementTasks={todos.done}
onDeleteTask={(taskIndex) => handleDeleteTask(taskIndex, 'done')}
setTodos={setTodos}
todos={todos} />
</div>
)
}
Todo容器:
import React, { useState, useEffect, useRef } from 'react'
import Task from './Task'
import Button from './Button';
import { useDrop } from 'react-dnd';
import { ItemTypes } from './constants';
export default function TodoContainer({
className,
title,
requirementTasks,
onDeleteTask,
setTodos,
todos,
onAddTask
}) {
const [isDraggingOver, setIsDraggingOver] = useState(false);
const ref = useRef();
const [{ isOver }, drop] = useDrop({
accept: ItemTypes.TASK,
drop: (item) => {
const { index: originalIndex, section: originalSection } = item;
const newSection = className; // The current section where the drop occurred
if (originalSection !== newSection) {
// Handle the task movement from originalSection to newSection
const updatedOriginalTasks = [...todos[originalSection]];
const updatedNewTasks = [...todos[newSection]];
const taskToMove = updatedOriginalTasks.splice(originalIndex, 1)[0];
if (className === 'done') {
taskToMove.check = 1;
} else {
taskToMove.check = 0; // Reset check status when moving tasks
}
updatedNewTasks.unshift(taskToMove);
setTodos((prevTodos) => ({
...prevTodos,
[originalSection]: updatedOriginalTasks,
[newSection]: updatedNewTasks,
}));
}
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
}),
});
useEffect(() => {
drop(ref); // Pass the ref of the container to the drop function
const handleDragOver = (event) => {
event.preventDefault();
setIsDraggingOver(true);
};
const handleDragLeave = () => {
setIsDraggingOver(false);
};
ref.current.addEventListener("dragover", handleDragOver);
ref.current.addEventListener("dragleave", handleDragLeave);
return () => {
ref.current.removeEventListener("dragover", handleDragOver);
ref.current.removeEventListener("dragleave", handleDragLeave);
};
}, [drop]);
const tasks = requirementTasks || [];
return (
<div className={`${className} todo-container ${isDraggingOver ? "dragging-over" : ""}`} ref={ref}>
<div className="todo-header">
<h3>{title}</h3>
<small>{tasks.length} Tasks</small>
</div>
{tasks.map((task, index) => (
<div className="tasks" key={index}>
<Task
context={task.title}
check={task.check}
handleDeleteTask={() => onDeleteTask(index)}
index={index}
setTodos={setTodos}
todos={todos}
requirementTasks={requirementTasks}
onAddTask={onAddTask}
section={className} />
</div>
))}
{className === 'done' ? '' : <Button
type='add'
onClickFun={() => { onAddTask({ title: '', check: 0 }); }} />}
</div>
)
}
任务组件
import React, { useState, useRef, useEffect } from 'react'
import { useDrag } from 'react-dnd';
import { ItemTypes } from './constants';
import Button from './Button';
export default function Task({ context, check, handleDeleteTask, index, setTodos, todos,
requirementTasks, section }) {
const [, drag] = useDrag({
type: ItemTypes.TASK, // Specify the item type
item: { index, section }, // Data to be transferred during the drag
});
const [isEditing, setIsEditing] = useState(false)
const [text, setText] = useState(context)
const textareaRef = useRef(null)
const [isChecked, setIsChecked] = useState(check);
const handleSpanClick = () => {
setIsEditing(true)
}
const handleTextChange = (event) => {
setText(event.target.value)
// Update the corresponding task in the state
const updatedTasks = [...requirementTasks];
updatedTasks[index].title = event.target.value;
setTodos({
...todos,
[section]: updatedTasks,
});
}
const handleBlur = () => {
setIsEditing(false)
}
const handleCheckboxChange = () => {
setIsChecked(prevIsChecked => !prevIsChecked); // Use functional update
// Create a copy of the task to move
const taskToMove = { ...requirementTasks[index] };
// Update the task's check property
taskToMove.check = isChecked ? 0 : 1; // Reverse the check values
// Create new instances of task arrays
const updatedSectionTasks = [...todos[section]];
const updatedTargetTasks = isChecked ? [...todos.todo] : [...todos.done]; // Reverse the target sections
// Remove task from the current section
updatedSectionTasks.splice(index, 1);
// Push the copied task to the target section
updatedTargetTasks.unshift(taskToMove);
// Update the state and localStorage
setTimeout(() => {
setTodos({
...todos,
[section]: updatedSectionTasks,
[isChecked ? "todo" : "done"]: updatedTargetTasks // Reverse the target section keys
});
}, 3000);
};
useEffect(() => {
if (isEditing && textareaRef.current) {
const textarea = textareaRef.current
const textLength = textarea.value.length
textarea.setSelectionRange(textLength, textLength)
textarea.focus()
}
}, [isEditing])
return (
<div className={`task ${isEditing ? '' : 'dis-flex'}`} ref={drag}>
<label className="checkbox-container">
<input
type="checkbox"
checked={isChecked}
onChange={handleCheckboxChange}
/>
<span className={`checkmark ${isChecked ? 'checked' : ''}`}></span>
</label>
{isEditing ? (
<textarea
ref={textareaRef}
value={text}
onChange={handleTextChange}
onBlur={handleBlur}
className="task-textarea"
/>
) : (
<span onClick={handleSpanClick} className={`${isChecked ? 'text-decoration' : ''}`}>{text ? text : (<input placeholder="new Task" className='new-task'></input>)}</span>
)}
<Button
type='delete'
onClickFun={() => {
handleDeleteTask(index);
}}
/>
</div>
)
}
但是当我选中或取消选中多个任务时,它只会更改 最后一个位置
根据您的描述的这一部分,您似乎正在描述在不使用
key
属性的情况下渲染列表时会发生什么。
React 需要推断列表中的特定项目发生了哪些变化,以便对正确的组件和 DOM 树进行相应的更新。
key
属性用于为列表中的组件提供唯一的键,以便 React 可以将正确的 DOM 节点与相应的组件正确匹配。只要你返回相同的元素类型,即使 props 改变,React 也不会更新没有 key
的列表中的组件/DOM 节点。
您可能希望确保您的待办事项列表状态是待办事项项的数组,然后映射它们,为地图内的父元素提供每个待办事项的唯一
key
属性。
return (
<div className='body'>
{todos.map((todo) => (
<TodoContainer
key={todo.id}
// your other props...
/>
))}
</div>
);