React 数组过滤未按预期工作
我正在开发一个简单的待办事项应用程序,但遇到了瓶颈。基本上,我的数组过滤不起作用。它实际上什么也没做,我也不知道为什么。我在应用程序中使用了 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'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-heuristic-shadow -7lz1o0?file =/src/app.js
745006007
068447172
感谢您分享您的完整代码!这有助于您了解正在发生的事情。
导致您头疼的罪魁祸首是:
<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
事件目标进行一些额外的条件检查,以查看是否点击了删除按钮 - 如果是,则尽早返回该函数。