React点击添加按钮触发列表无限渲染的问题解决
问题描述
我有两个React组件——Contacts和ContactItem。Contacts组件包含展示联系人的div和“添加”按钮;ContactItem组件包含选择社交网络的select、输入社交账号的textarea,以及删除按钮。我需要实现添加和删除联系人项的功能,但点击添加按钮后会触发列表无限渲染的问题,请问该如何修复?
相关代码如下:
Contacts组件
const Contacts = () => { const [contactItems, setContactItems] = useState([ { index: 0, key: 0, id: 0, }, ]); const addItemHandler = (event) => { event.preventDefault(); const id = uuidv4(); setContactItems(() => [ ...contactItems, { index: contactItems.length, key: id, id, }, ]); }; const removeItemHandler = (id) => { setContactItems((contactItems) => contactItems.filter((el) => el.id !== id) ); }; return ( <> <div className={stylesCenter.channels}> {contactItems.map((item) => ( <ContactItem index={item.index} key={item.key} id={item.id} removeItem={removeItemHandler} /> ))} </div> <div> <button onClick={addItemHandler} > <img src="plus.svg" alt="plus logo" /> </button> </div> </> ); };
ContactItem组件
const ContactItem = ({ index, removeItem }) => { console.log("child render", index); return ( <div className={stylescenter.fullChannelControll}> <select className={stylescenter.selecterOptions} name="optionSelected"> {options.map((el) => ( <option key={el.value} value={el.value}> {el.label} </option> ))} </select> <div className={stylescenter.detailsAndInputAndDelete}> <textarea maxLength="100" rows="2" /> <div className={stylescenter.removeButtons}> {index !== 0 && ( <span> <IconButton onClick={removeItem(index)}> <img src="bin.svg" alt="bin logo" /> </IconButton> </span> )} </div> </div> </div> ); };
修复方案
1. 解决删除按钮的立即执行问题(核心原因)
ContactItem组件中,onClick={removeItem(index)}会在组件渲染时立即调用removeItem函数,而非点击时触发。这会直接触发状态更新,导致组件重新渲染,进而再次调用removeItem,形成无限循环。
同时,原代码用index作为删除标识,但index会随列表项删除发生变化,应该使用唯一的id定位待删除项,避免删除错误。
修改ContactItem组件的删除按钮部分:
// 新增接收id参数 const ContactItem = ({ index, id, removeItem }) => { console.log("child render", index); return ( // ... 其他代码不变 <div className={stylescenter.removeButtons}> {index !== 0 && ( <span> {/* 用箭头函数包裹,点击时才调用removeItem并传入正确的id */} <IconButton onClick={() => removeItem(id)}> <img src="bin.svg" alt="bin logo" /> </IconButton> </span> )} </div> // ... 其他代码不变 ); };
2. 修正添加项的状态更新逻辑
原addItemHandler使用外部的contactItems更新状态,可能因闭包问题获取到旧状态值。改用React状态更新的函数式写法,确保基于最新状态更新:
修改Contacts组件的addItemHandler:
const addItemHandler = (event) => { event.preventDefault(); const id = uuidv4(); // 使用函数式更新,接收最新的state作为参数 setContactItems((prevItems) => [ ...prevItems, { index: prevItems.length, key: id, id, }, ]); };
3. 可选优化:减少不必要重渲染
用React.memo包裹ContactItem组件,避免父组件状态变化时所有子组件强制重渲染:
const ContactItem = React.memo(({ index, id, removeItem }) => { // ... 组件内容不变 });
最终修改后的完整代码
Contacts组件
const Contacts = () => { const [contactItems, setContactItems] = useState([ { index: 0, key: 0, id: 0, }, ]); const addItemHandler = (event) => { event.preventDefault(); const id = uuidv4(); setContactItems((prevItems) => [ ...prevItems, { index: prevItems.length, key: id, id, }, ]); }; const removeItemHandler = (id) => { setContactItems((contactItems) => contactItems.filter((el) => el.id !== id) ); }; return ( <> <div className={stylesCenter.channels}> {contactItems.map((item) => ( <ContactItem index={item.index} key={item.key} id={item.id} removeItem={removeItemHandler} /> ))} </div> <div> <button onClick={addItemHandler}> <img src="plus.svg" alt="plus logo" /> </button> </div> </> ); };
ContactItem组件
const ContactItem = React.memo(({ index, id, removeItem }) => { console.log("child render", index); return ( <div className={stylescenter.fullChannelControll}> <select className={stylescenter.selecterOptions} name="optionSelected"> {options.map((el) => ( <option key={el.value} value={el.value}> {el.label} </option> ))} </select> <div className={stylescenter.detailsAndInputAndDelete}> <textarea maxLength="100" rows="2" /> <div className={stylescenter.removeButtons}> {index !== 0 && ( <span> <IconButton onClick={() => removeItem(id)}> <img src="bin.svg" alt="bin logo" /> </IconButton> </span> )} </div> </div> </div> ); });
内容的提问来源于stack exchange,提问作者djeff
相关产品推荐
相关产品推荐

