React 数组过滤未按预期工作

React array filtering is not working as expected

我正在开发一个简单的待办事项应用程序,但遇到了困难。基本上,我的数组过滤不起作用。它实际上什么也没做,我也不知道为什么。我在应用程序中使用 Material UI,我怀疑与此相关但无法完全弄清楚。

我正在尝试通过单击触发“deleteTodo”功能的垃圾桶图标来删除一个待办事项。但它并没有从待办事项中删除它。实际上,正如我所说,它什么都不做。我将待办事项保存在 localStorage 中。

这是我的删除一个待办事项功能:

    function deleteTodo(id) {
        setTodos(todos.filter((todo,i,arr) => {
            console.log("id:", id)
            console.log("todo.id:",todo.id)
            console.log("are equal:", todo.id === id)
            console.log(i, arr)
            return (todo.id !== id)
        }))
    }

控制台输出:

[Log] id: – "37dbcd88d5a"
[Log] todo.id: – "37dbcd88d5a"
[Log] are equal: – true
[Log] 1 – [{text: "two", done: false, id: "7dbcd88d5a3"}, {text: "one", done: false, id: "37dbcd88d5a"}] (2)

我的整个组件:

import { uid } from 'uid'
import { useState , useEffect, useLayoutEffect, useRef } from "react"
import { Card, CardContent, Modal, List, ListItem, Box, Button, IconButton, TextField, Typography } from "@mui/material"
import styles from "../styles/Todos.module.css"
import { TransitionGroup } from 'react-transition-group';
import ReportProblemIcon from '@mui/icons-material/ReportProblem';
import DeleteIcon from '@mui/icons-material/Delete';
import EditIcon from '@mui/icons-material/Edit';


export default function Todos () {
    const [ text, setText ] = useState("")
    const [ todos, setTodos ] = useState([])
    const [ showClearTodosModal, setShowClearTodosModal] = useState(false)
    const inputRef = useRef()

    useEffect(() => {
        // localStorage.todos && console.log('b1:',JSON.parse(localStorage.todos))
        if (localStorage.todos) {
            // localStorage.todos && console.log(JSON.parse(localStorage.todos))
            setTodos(JSON.parse(localStorage.todos))
        } else {
            localStorage.todos = []
        }
        // localStorage.todos && console.log('a1:',JSON.parse(localStorage.todos))
    }, [])
    useEffect(() => {
        if (todos && todos.length > 0) {
            localStorage.todos && console.log('b2:',JSON.parse(localStorage.todos))
            localStorage.setItem("todos", JSON.stringify(todos))
            localStorage.todos && console.log('a2:',JSON.parse(localStorage.todos))
        }
    }, [todos])

    function handleSubmit(e) {
        e.preventDefault()
        const id = uid()
        setTodos([{text, done:false, id:id}, ...todos])
        setText("")
    }

    function markDone(e) {
        setTodos(todos.map(todo => {
            if (e.target.innerText === todo.text) {
                return {...todo, done:!todo.done}
            } else {
                return todo
            }
        }))
    }

    function deleteTodo(id) {
        setTodos(todos.filter((todo,i,arr) => {
            console.log("id:", id)
            console.log("todo.id:",todo.id)
            console.log("are equal:", todo.id === id)
            console.log(i, arr)
            return (todo.id !== id)
        }))
    }
    function deleteAll() {
        setTodos([])
        setShowClearTodosModal(false)
        localStorage.removeItem("todos")
    }

    return (
        <Box>
            <form
                onSubmit={handleSubmit}
            >
                <TextField 
                    className={styles.entryfield}
                    label="Add a todo"
                    autoFocus
                    value={text}
                    onChange={(e) => setText(e.target.value)}
                    ref={inputRef}
                />
            </form>

            <Button 
                 color="primary" 
                 aria-label="upload picture" 
                 component="span"
                    className={styles.clearall} 
                    onClick={() => setShowClearTodosModal(true)}
                startIcon={<ReportProblemIcon />}
            >
                Delete All Todos
            </Button>
            <Modal
              open={showClearTodosModal}
              onClose={() => setShowClearTodosModal(false)}
              aria-labelledby="delete all todos"
              aria-describedby="delete all todos"
              className={styles.deleteallmodal}
            >
              <Box className={styles.modalbox}>
                <Typography id="modal-modal-title" variant="h6" component="h2">
                    Delete all todos?
                </Typography>
                <Typography id="modal-modal-description" sx={{ mt: 2 }}>
                    Are you sure you want to delete all todos? This action is irreversable and you will lose all of your todos.
                </Typography>
                <Button
                    onClick={() => setShowClearTodosModal(false)}
                    variant="contained"
                    sx={{m:1}}
                >
                Nah, don't delete my todos
                </Button>
                <Button 
                    onClick={deleteAll}
                    variant="contained"
                    startIcon={<ReportProblemIcon />}
                    sx={{
                        m:1,
                        color: "maroon",
                    }}
                >
                Yes I&apos;m sure delete all of them
                </Button>
              </Box>
            </Modal>

            <List>
            {todos && todos.map(todo =>(
                <ListItem 
                    key={todo.id}
                    onClick={markDone}
                >
                    <Card className={styles.card}
                    >
                            <IconButton 
                                className={styles.icons}
                                onClick={() => {deleteTodo(todo.id)}}
                                aria-label="delete"
                            >
                              <DeleteIcon fontSize="small"/>
                            </IconButton>
                            <IconButton 
                                className={styles.icons}
                                aria-label="edit"
                            >
                              <EditIcon fontSize="small"/>
                            </IconButton>
                            <Typography
                                    variant="body1"
                                    style= {{
                                        color: todo.done ? "#555" : "",
                                        margin: 10,
                                    }}
                            >
                                {todo.text}
                            </Typography>
                    </Card>
                </ListItem>
                )
            )}
            </List>
        </Box>
    )
}

这是您组件的工作代码和框。请将您的代码与我的示例对齐,并确保您的代码中没有任何其他错误的实现 https://codesandbox.io/s/heuristic-shadow-7lz1o0?file=/src/App.js

import { useState } from "react";
import "./styles.css";

export default function App() {
  const [todos, setTodos] = useState([
    { text: "two", done: false, id: "7dbcd88d5a3" },
    { text: "one", done: false, id: "37dbcd88d5a" },
    { text: "one", done: false, id: "643dbcd88d5a" }
  ]);

  function deleteTodo(id) {
    setTodos(
      todos.filter((todo, i, arr) => {
        console.log("id:", id);
        console.log("todo.id:", todo.id);
        console.log("are equal:", todo.id === id);
        console.log(i, arr);
        return todo.id !== id;
      })
    );
  }
  return (
    <div className="App">
      {todos &&
        todos.map((todo) => (
          <div key={todo.id}>
            <div>
              <button
                onClick={() => {
                  deleteTodo(todo.id);
                }}
                aria-label="delete"
              >
                {todo.id}
              </button>
            </div>
          </div>
        ))}
    </div>
  );
}

let testArray = [{text: "two", done: false, id: "7dbcd88d5a3"}, {text: "one", done: false, id: "37dbcd88d5a"},
{text: "one", done: false, id: "643dbcd88d5a"}]

function deleteTodo(id) { 
        console.log(testArray.filter((todo,i,arr) => {
        return (todo.id !== id)}))
}

deleteTodo('7dbcd88d5a3');

感谢您分享您的完整代码!了解正在发生的事情很有帮助。

让您头疼的罪魁祸首是:

<ListItem 
  key={todo.id}
  onClick={markDone}
                >
                    <Card className={styles.card}
                    >
                            <IconButton 
                                className={styles.icons}
                                onClick={() => {deleteTodo(todo.id)}}
                                aria-label="delete"
                            >

具体来说,onClick={markDone} 在 ListItem 上。您将 markDone 函数放在整个列表项上。因此,单击 IconButton 也会触发 ListItem 事件。

我知道你有 e.preventDefault() 但不幸的是这里的顺序颠倒了事件冒泡(从里到外开始,你需要 stopPropagation 而不是 preventDefault):我在 deleteTodo 和 markDone 中放置了一些日志来指示开始和每个函数的结尾,这是顺序。这是输出:

Todos.tsx:82 deleteTodo start
Todos.tsx:86 deleteTodo end
Todos.tsx:66 markdone start
Todos.tsx:76 markdone end

因此,可能发生的反应是按顺序执行 setTodos 或对它们进行批处理,但无论哪种方式,markDone 的 setTodos 看起来都优先,而 setTodo 又只是一个相同值的映射。因此,为什么单击删除按钮不会导致任何更改并保留相同的待办事项。

可能的解决方案可能包括

a) 将事件作为参数添加到函数顶部的 deleteTodo 和 运行 e.stopPropagation() 以防止事件进一步冒泡。

function deleteTodo(e, id) {
    e.stopPropagation();
    console.log('deleteTodo start');
    setTodos(
      todos.filter((todo, i, arr) => {
        return todo.id !== id;
      })
    );
    console.log('deleteTodo end');
  }

并设置您的 IconButton:

<IconButton
                  onClick={(e) => {
                    deleteTodo(e, todo.id);
                  }}

这应该有助于阻止事件进一步冒泡(我确认这在本地有效)。

b) 您可以删除 onClick={markDone} 并添加一个单独的按钮来执行标记完成操作(确认删除 onClick 可以使您的删除方法起作用)

c) 你保留 markDone 但对 e 事件目标做一些额外的条件检查以查看删除按钮是否被击中 - 如果是,return 在函数的早期。