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