如何修复ReactJS待办事项列表应用重复添加项的问题?
React待办事项添加重复项问题
概述
开发ReactJS待办事项列表应用,期望每次操作仅添加一个列表项。
实际表现
添加待办事项时,该项会被重复添加两次。
问题代码
App.js
import Header from "./components/Header"; import List from "./components/List"; import {useState} from "react"; function App() { const [items, setItem] = useState(function () { const storageItemsString = localStorage.getItem('items'); if (storageItemsString) { return JSON.parse(storageItemsString); } else { return []; } } ); function addItem(fieldValue) { setItem( (prevItems) => { const newItems = prevItems.concat(fieldValue); localStorage.setItem('items', JSON.stringify(newItems)); return newItems; }); } return ( <div className="App"> <div className="wrapper"> <Header/> <List addItem={addItem} items={items}/> </div> </div> ); } export default App;
List.jsx
import React, {useEffect} from 'react'; import {Button, TextField} from "@mui/material"; import ListItem from "./listItem"; function List(props) { useEffect(() => { const textField = document.querySelector('#list-field'); const button = document.querySelector('.button'); button.addEventListener('click', function(e) { if (textField.value !== '') { props.addItem(textField.value); } }); }, [props]); return ( <div className="list-section"> <form action="#" className="list-form"> <TextField variant="outlined" label="Enter something" id="list-field" multiline rows={4}/> <Button variant="contained" className="button">Add item</Button> </form> <div className="list-items"> {props.items.map( (item) => <ListItem text={item} />)} </div> </div> ); } export default List;
ListItem.jsx
import React from 'react'; import DeleteIcon from '@mui/icons-material/Delete'; function ListItem(props) { return ( <div className="list-item"> <div className="item-content">{props.text}</div> <span className="delete-icon"> <DeleteIcon /> </span> </div> ); } export default ListItem;
问题原因
- 重复绑定点击事件:
List组件中useEffect依赖为[props],每次添加待办项后items更新,props发生变化,useEffect会重新执行,给按钮重复绑定click事件,导致点击一次触发多次addItem。 - 表单默认提交行为:按钮在
<form>标签内,默认type="submit",点击时会触发表单的默认提交行为,进一步加重重复触发的问题。
修复方案
推荐方案:使用React原生事件处理(符合React开发范式)
修改List.jsx,移除原生DOM事件绑定,改用React内置事件处理,同时阻止表单默认行为:
import React, {useRef} from 'react'; import {Button, TextField} from "@mui/material"; import ListItem from "./listItem"; function List(props) { const textFieldRef = useRef(null); const handleAddItem = (e) => { // 阻止表单默认提交行为 e.preventDefault(); const value = textFieldRef.current.value.trim(); if (value) { props.addItem(value); // 清空输入框,提升体验 textFieldRef.current.value = ''; } }; return ( <div className="list-section"> <form className="list-form" onSubmit={handleAddItem}> <TextField variant="outlined" label="Enter something" id="list-field" multiline rows={4} inputRef={textFieldRef} /> <Button variant="contained" type="submit">Add item</Button> </form> <div className="list-items"> {/* 添加key属性,避免React警告 */} {props.items.map( (item, index) => <ListItem key={index} text={item} />)} </div> </div> ); } export default List;
备选方案:清理useEffect中的事件监听
若坚持使用原生DOM绑定,需在useEffect的清理函数中移除事件监听,避免重复绑定:
import React, {useEffect} from 'react'; import {Button, TextField} from "@mui/material"; import ListItem from "./listItem"; function List(props) { useEffect(() => { const textField = document.querySelector('#list-field'); const button = document.querySelector('.button'); const handleClick = (e) => { e.preventDefault(); if (textField.value.trim()) { props.addItem(textField.value.trim()); } }; button.addEventListener('click', handleClick); // 组件卸载或props变化时移除事件监听 return () => button.removeEventListener('click', handleClick); }, [props]); return ( <div className="list-section"> <form action="#" className="list-form"> <TextField variant="outlined" label="Enter something" id="list-field" multiline rows={4}/> <Button variant="contained" className="button">Add item</Button> </form> <div className="list-items"> {props.items.map( (item, index) => <ListItem key={index} text={item} />)} </div> </div> ); } export default List;
额外说明
推荐使用第一种方案,React内置事件处理已封装跨浏览器兼容和自动清理逻辑,更符合声明式开发模式,代码简洁易维护。
内容的提问来源于stack exchange,提问作者Roman
相关产品推荐
相关产品推荐

