开发者问题收集

React 数组过滤未按预期工作

2022-05-07
1231

我正在开发一个简单的待办事项应用程序,但遇到了瓶颈。基本上,我的数组过滤不起作用。它实际上什么也没做,我也不知道为什么。我在应用程序中使用了 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>
    )
}

2个回答

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

745006007 068447172
Evren
2022-05-07

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

导致您头疼的罪魁祸首是:

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

具体来说,ListItem 上的 onClick={markDone 。您将 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

所以可能发生的情况是,React 要么按顺序执行 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 事件目标进行一些额外的条件检查,以查看是否点击了删除按钮 - 如果是,则尽早返回该函数。

Azarro
2022-05-07