You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复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;

问题原因

  1. 重复绑定点击事件:List组件中useEffect依赖为[props],每次添加待办项后items更新,props发生变化,useEffect会重新执行,给按钮重复绑定click事件,导致点击一次触发多次addItem。
  2. 表单默认提交行为:按钮在<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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.31 18:15:59