更新列表中呈现的 React 组件时,列表中不同项目的组件会发生变化

问题描述 投票:0回答:1

我有一个任务组件,里面有一个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>
)
}
reactjs state refresh page-refresh react-dnd
1个回答
1
投票

但是当我选中或取消选中多个任务时,它只会更改 最后一个位置

根据您的描述的这一部分,您似乎正在描述在不使用

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>
);

资源

https://react.dev/learn/rendering-lists

https://kentcdodds.com/blog/understanding-reacts-key-prop

© www.soinside.com 2019 - 2024. All rights reserved.