I have a Task Component and in there have a handleCheckboxChange that if I checked the tast after 3 seconds it should go to the done list and if unchecked it should go at the begining of Todo list and it work fine but when I checked or unchecked more than one task it just change the last one position how should I fix that? my Body component:
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>
)
}
TodoContainer:
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>
)
}
Task component
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>
)
}
but when I checked or unchecked more than one task it just change the last one position
You seem to be describing what can happen when you render a list without using the key
prop.
React needs to infer what changes happened to the specific item in your list.
So long as you return the same element type, even if props change, React will not be able to update the corresponding components/DOM nodes in a list if you don't provide a key
.
You probably want to ensure your todo list state is an array of your todo items and then map over them, providing the parent element inside your map with a unique key
prop for each todo.
return (
<div className='body'>
{todos.map((todo) => (
<TodoContainer
key={todo.id}
// your other props...
/>
))}
</div>
);