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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 07:10:11