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
相关产品推荐
相关产品推荐

