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

React点击删除按钮后父组件未移除已删列表项的解决方法

问题描述
  • 项目技术栈:PHP + React,目标实现基于AJAX调用的CRUD系统
  • 现有界面:已完成界面截图
  • 故障现象:点击删除按钮时,删除请求可正常发送,服务端对应数据行也能成功删除,但父组件刷新后,列表中并未移除已删除的条目。
故障时代码实现

单行用户组件(User)

class User extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            isEditing: false,

            id: this.props.id,
            username: this.props.username,
            email: this.props.email,
            password: this.props.password,

            refID: '',
            refUsername: '',
            refEmail: '',
            refPassword: ''   
        };

        this.refID = this.props.id;
        this.refUsername = this.props.username;
        this.refEmail = this.props.email;
        this.refPassword = this.props.password;

        this.handleDelete = this.handleDelete.bind(this);
    }


    handleEdit() {
        this.setState({isEditing: true});
    }


    handleDelete() {
        $.ajax({  
            type: 'POST',
            url: 'http://localhost/CRUD/server/api/delete.php',
            data: {
                id: this.state.id
            },
            success: function(response) {
                toast.success('User deleted successfully!', {
                    position: "bottom-left",
                    autoClose: 3000,
                    hideProgressBar: true,
                    closeOnClick: true,
                    pauseOnHover: true,
                    draggable: true,
                    progress: undefined,
                    icon: CheckCircleIcon
                });
                console.log(response);

                // Remove the user from the list.
                
            },
            error: function(jqXHR, textStatus, errorThrown){
                toast.error('Error deleting user!', {
                    position: "bottom-left",
                    autoClose: 3000,
                    hideProgressBar: true,
                    closeOnClick: true,
                    pauseOnHover: true,
                    draggable: true,
                    progress: undefined,
                    icon: CheckCircleIcon
                });
                console.log(' response: ', jqXHR, textStatus, errorThrown)

                return false;
            }
        })

        this.props.rerender();
    }

    handleCancel() {
        this.setState({
            isEditing: false
        });
    }

    handleSave() {
        $.ajax({  
            type: 'POST',
            url: 'http://localhost/CRUD/server/api/update.php', 
            data: {
                id: this.refID,
                username: this.refUsername,
                email: this.refEmail,
                password: this.refPassword
            },
            success: function() {
                toast.success("Successfully updated the user information.",{
                    position: "bottom-left",
                    autoClose: 3000,
                    hideProgressBar: true,
                    closeOnClick: true,
                    pauseOnHover: true,
                    draggable: true,
                    progress: undefined,
                    icon: CheckCircleIcon
                    });
            },
            error: function(jqXHR, textStatus, errorThrown){
                toast.error("Error updating the user information.");
                console.log(' response: ', jqXHR, textStatus, errorThrown)
            }
        })
        // Update the state with the new values. As well as changing the display back to the original.
        this.setState({isEditing: false, id: this.refID, username: this.refUsername, email: this.refEmail, password: this.refPassword});
    }

    render() {
        let DisplayMode, EditMode;

        DisplayMode = (
            <div className="Row">
                <div className="Cell C1" id='DataID'>
                    <p>{this.state.id}</p>
                </div>
                <div className="Cell C2" id='DataUsername'>
                    <p>{this.state.username}</p>
                </div>
                <div className="Cell C3" id='DataEmail'>
                    <p>{this.state.email}</p>
                </div>
                <div className="Cell C4" id='DataPassword'>
                    <p>{this.state.password}</p>
                </div>
                <div className="Cell C5" id='DataActions'>
                    <div className="Button Secondary Edit">
                        {/* On click of the Edit button, switch the cells with inputs, and change the buttons to save button*/}
                        <EditIcon onClick={() => this.handleEdit()} />
                    </div>
    
                    <div className="Button Primary Delete">
                        <DeleteIcon onClick={() => this.handleDelete()} />
                    </div>
                </div>
            </div>
        );

        EditMode = (
            <div className="Row EditMode">

                <div className="Cell C1" id="DataID">
                    <p>{this.props.id}</p>
                </div>

                <div className="Cell C2 Editing" id="DataUsername">
                    <input type='text' placeholder='Edit username' defaultValue={this.state.username} onChange={e => {this.refUsername = e.target.value}}/>
                </div>

                <div className="Cell C3 Editing" id="DataEmail">
                    <input type='text' placeholder='Edit e-mail' defaultValue={this.state.email} onChange={e => {this.refEmail = e.target.value}}/>
                </div>

                <div className="Cell C4 Editing" id="DataPassword">
                    <input type='text' placeholder='Edit password' defaultValue={this.state.password} onChange={e => {this.refPassword = e.target.value}}/>
                </div>

                <div className="Cell C5" id="DataActions">
                    <div className="Button Secondary Edit">
                        <CancelIcon onClick={() => this.handleCancel()}/>
                    </div>
                    <div className="Button Primary Delete">
                        <SaveIcon onClick= {() => this.handleSave()} />
                    </div>
                </div>

            </div>
        );
        
        const CurrentMode = () => { 
            return(
                this.state.isEditing ? EditMode : DisplayMode
            )
         };

        return (
            <>
                <CurrentMode/>
            </>
        );
    };
};

用户列表父组件(UsersListView)

class UsersListView extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            isLoaded: false,
            error: null,
            users: [],
            counter: 0,
            isLoading: false
        };

        this.handleRefresh = this.handleRefresh.bind(this);
        
    };

    rerender = () => {
        this.forceUpdate();
    };
    forceUpdate = () => {
        this.setState((state) => ({
            counter: state.counter + 1,
            isLoaded: false
        }));
    };

    componentDidMount() {
        this.handleRefresh();
    }

    componentDidUpdate() {
        console.log("Updated");
        () => {
            this.handleRefresh();
        }, [this.state.users]
    };


    handleRefresh() {
        fetch("http://localhost/CRUD/server/api/read.php")
        .then(res => res.json())
        .then((result) => {
            this.setState({
                isLoading: false,
                isLoaded: true,
                users: result
            });
        },
        (error) => {
            this.setState({
                isLoading: false,
                isLoaded: false,
                error
            });
            console.log(error)
        }
        )

        this.forceUpdate();
    }

    render() {

        return this.state.isLoaded ?
                // If the data is loaded, render the users.        
                
                    this.state.users.map((user) => {
                        return (
                            <User 
                                rerender={this.rerender} 
                                key={user.id} 
                                id={user.id} 
                                username={user.username} 
                                email={user.email} 
                                password={user.password}
                            />
                        )
                    }) 
                // If the data is not loaded, display a loading message.
                : (

                <div className='User-Loading'>
                   <div className='Loading'>
                        <div className="loader">
                            <span></span>
                            <span></span>
                            <span></span>
                            <span></span>
                            <span></span>
                            <span></span>
                        </div>
                    </div>

                    <div className='User-Loading-Text'>
                        <h3>Loading...</h3>

                        <p>Taking too long? Check the API link if is connected.</p>

                        <div className='button' onClick={this.handleRefresh}>
                            <RefreshIcon/>
                            Retry
                        </div>
                    </div>
                </div>
                
                )
    }
}

export default UsersListView;
问题根因
  • 异步时序错误:$.ajax是异步请求,原代码在发起请求后立刻调用this.props.rerender()触发列表刷新,此时服务端删除操作还未完成,拉取到的仍是包含已删除条目的旧数据;删除请求的success回调中没有写任何刷新列表的逻辑,请求成功后不会触发二次数据拉取。
  • 生命周期写法错误:类组件的componentDidUpdate不支持函数组件useEffect的依赖数组写法,原代码中写的() => {this.handleRefresh();}, [this.state.users]是无效代码,根本不会执行,监听用户列表变化自动刷新的逻辑完全失效。
  • 状态更新竞态:handleRefresh方法中,fetch请求刚发起还没返回结果时就调用了this.forceUpdate()将isLoaded设为false,加载状态和数据返回时机不匹配,容易出现渲染异常。
  • 逻辑耦合不当:删除数据的业务逻辑放在子组件中,子组件无法直接修改父组件维护的users列表state,只能通过强制重渲染的方式触发刷新,链路长且容易出问题。
已验证解决方案

将handleDelete()函数迁移到父类组件中,通过子组件的props调用该方法,删除请求成功后直接更新父组件存储的用户列表state,即可解决每次删除操作后列表无法移除对应行的核心问题。

优化参考:

  • 所有涉及用户数据增删改的逻辑统一收拢到父组件维护,子组件只负责渲染和触发回调,降低组件耦合度。
  • 所有数据请求的状态更新(加载态、数据、错误)统一放在请求的回调中执行,不要在请求发起阶段就修改状态,避免异步竞态。
  • 类组件的componentDidUpdate如果需要做变量监听,手动对比前后的props/state值再执行对应逻辑,不要混用函数组件的语法。

内容的提问来源于stack exchange,提问作者Yassine Chettouch

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 09:15:34