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

React动态渲染JSX元素点击事件无法正确更新currentSelections状态

按需加载内容树的节点选中状态异常问题

我是React新手,正在实现一个可增量加载的内容树:初始仅渲染单个节点,用户点击+按钮时调用API加载该节点的子节点(仅加载未加载过的)并添加为内部节点,以此实现按需加载,避免一次性加载数千条数据以提升性能。

节点展开功能运行正常,但双击节点的selectNode函数存在异常:

  • 每次选中节点时,currentSelections状态始终被重置为当前选中项,函数内访问currentSelections时始终为undefined,无法实现选中项的追加(预期效果:[内容]→[内容,媒体库]→[内容,媒体库,布局],实际效果:[内容]→[媒体库]→[布局])
  • 特殊情况:选中节点后关闭模态框,再次打开时之前的选中值会保留,新点击的节点会被追加到currentSelections中,但再次点击其他节点时,仅会保留初始选中项和最新选中项,无法继续追加更多项

相关代码

const [currentSelections, setCurrentSelections] = useState<any[]>();
const [contentMainRoot, setContentMainRoot] = useState<Root>();

const resetTree = () => {
  if (contentMainRoot) {
    contentMainRoot.render(<ul id={sitecoreRootId}></ul>);
  }
};

useEffect(() => {
  const rootElem = document.getElementById(sitecoreRootId);
  if (!rootElem) return;
  setContentMainRoot(createRoot(rootElem));
  resetTree();
}, [activeInstance]);

const toggleNode = async (e: any) => {
  if (!activeInstance) return;
  if (!e.target.classList.contains('loaded')) {
    const id = e.target.parentElement.getAttribute('data-id');
    const results = await GetItemChildren(activeInstance, id);
    const children = results.children;
    console.log(children);

    // append ul root
    let root = null;
    if (id + '-root' === sitecoreRootId && contentMainRoot) {
      root = contentMainRoot;
    } else {
      const rootElem = document.getElementById(id + '-root');
      if (!rootElem) return;
      root = createRoot(rootElem);
    }
    const innerTree = getInnerBrowseTree(id, children);
    root?.render(innerTree);

    e.target.classList.add('loaded');
    e.target.classList.add('open');
  } else {
    if (e.target.classList.contains('open')) {
      e.target.classList.remove('open');
    } else {
      e.target.classList.add('open');
    }
  }
};

const selectNode = (e: any) => {
  const id = e.target.parentElement
    .getAttribute('data-id')
    .replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, '$1-$2-$3-$4-$5');
  const name = e.target.parentElement.getAttribute('data-name');
  console.log(id);

  if (e.target.classList.contains('selected')) {
    e.target.classList.remove('selected');
    // remove id
  } else {
    let selectedItem = { itemId: id, name: name };
    let selectedItems: any[] =
      currentSelections === undefined ? [selectedItem] : currentSelections?.concat(selectedItem);
    setCurrentSelections(selectedItems);

    e.target.classList.add('selected');
  }
};

const getInnerBrowseTree = (id: string, children: any[]) => {
  return (
    <ul id={id}>
      {children.map((child, index) => (
        <li key={index} data-name={child.name} data-id={child.itemId}>
          {child.hasChildren && <a className="browse-expand" onClick={(e) => toggleNode(e)}></a>}
          <a className="sitecore-node" onDoubleClick={(e) => selectNode(e)}>
            {child.name}
          </a>
          <ul id={child.itemId + '-root'}></ul>
        </li>
      ))}
    </ul>
  );
};

return (
  <>
    {browseContentOpen && (
      <div id="content-tree" className="content-tree">
        <div className="inner">
          <div className="flex items-center gap-2 mt-4">
            <ul>
              <li data-name="sitecore" data-id="{11111111-1111-1111-1111-111111111111}">
                <a className="browse-expand" onClick={(e) => toggleNode(e)}></a>
                <a className="sitecore-node" onDoubleClick={(e) => selectNode(e)}>
                  sitecore
                </a>
                <ul id={sitecoreRootId}></ul>
              </li>
            </ul>
            <ul>
              {currentSelections &&
                currentSelections?.map((item, index) => (
                  <li data-id={item.itemId} data-name={item.name} key={index}>
                    {item.name}
                  </li>
                ))}
            </ul>
            <Button variant="ghost" size="sm" onClick={() => setBrowseContentOpen(false)}>
              Close
            </Button>
          </div>
        </div>
      </div>
    )}
</>
)

解决方案

1. 使用函数式更新获取最新状态

React状态更新是异步的,直接访问currentSelections会拿到旧值。改用setCurrentSelections的函数式更新,确保每次基于最新状态修改:

const selectNode = (e: any) => {
  const id = e.target.parentElement
    .getAttribute('data-id')
    .replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, '$1-$2-$3-$4-$5');
  const name = e.target.parentElement.getAttribute('data-name');

  if (e.target.classList.contains('selected')) {
    e.target.classList.remove('selected');
    // 移除选中项也用函数式更新
    setCurrentSelections(prev => prev?.filter(item => item.itemId !== id));
  } else {
    const selectedItem = { itemId: id, name: name };
    // 基于最新状态追加,同时避免重复添加
    setCurrentSelections(prev => {
      if (!prev) return [selectedItem];
      if (prev.some(item => item.itemId === id)) return prev;
      return [...prev, selectedItem];
    });
    e.target.classList.add('selected');
  }
};

2. 初始化状态为空数组而非undefined

原代码中currentSelections初始化为undefined,导致判断逻辑复杂且容易出错,改为初始化为空数组:

const [currentSelections, setCurrentSelections] = useState<any[]>([]);

3. 避免手动操作DOM渲染子树

原代码使用createRoot和root.render手动渲染子树,不符合React数据流模式,容易导致状态与DOM不同步。建议维护一个treeNodes状态存储已加载的节点数据,通过状态驱动渲染,替代手动DOM操作。


内容的提问来源于stack exchange,提问作者Erica Stockwell-Alpert

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 01:35:56