у меня есть вот такой код
import React, { RefObject, useCallback, useEffect, useRef, useState } from 'react';
import { ContextMenu } from 'primereact/contextmenu';
import {
  Tree,
  TreeCheckboxSelectionKeys,
  TreeExpandedEvent,
  TreeNodeClickEvent,
  TreeNodeDoubleClickEvent,
  TreeSelectionEvent,
} from 'primereact/tree';
import { CustomTreeNode, HotKeyParams, isCustomTreeNode } from '../types';
import { ObjectEntity } from '../../../shared-objects-client/src/shared/g';
import { TreeNode } from 'primereact/treenode';
import { useObjectTree } from '../hooks/objectTree-hook';
import { FieldName, HotKeys } from '../constants';

interface Props {
  sources: Array<ObjectEntity>;
  selectionMode: 'checkbox' | 'single';
  onSelectObject: (object: TreeCheckboxSelectionKeys | string | null) => void;
  loading?: boolean;
  onDoubleNodeClick?: (object: string | null) => void;
  searchText?: string;
  affiliation?: string;
  contextMenuRef?: RefObject<ContextMenu> | null;
  setNodeKey?: (key: string | undefined) => void;
  externalSelectedObject?: string | null;
  types?: Array<string>;
  onDragStart?: (node: string, event: React.DragEvent<HTMLDivElement>) => void;
  onFinishTreeBuild?: () => void;
  expandedObjects?: string[];
  onKeyPress?: (event: KeyboardEvent) => void;
  overriddenKeyPress?: string[];
  hotKeyFromContextMenu?: HotKeyParams | null;
  propagateSelection?: boolean;
  onClickNodeInCheckboxMode?: (selectedObjectId: string | undefined) => void;
}

/**
 * Компонент дерева
 * @constructor
 * @param sources — источники данных
 * @param selectionMode — режим выбора узлов в дереве
 * @param onSelectObject — обрабатывает выбранный объект(ы)
 * @param loading — состояние лоадера
 * @param searchText — тест поиска по дереву
 * @param affiliation — принадлежность объектов (объекты, документы и т.п.)
 * @param contextMenuRef — ссылка на контекстное меню
 * @param setNodeKey — устанавливает id, выбранного узла для открытия контекстного меню
 * @param externalSelectedObject — выбранный из вне объект
 * @param onDoubleNodeClick — функция обработки двойного клика по узлу
 * @param onDragStart — функция обработки начала переноса объекта
 * @param onFinishTreeBuild — функция обработки завершения построения дерева
 * @param expandedObjects — развернутые узлы
 * @param onKeyPress — функция обработки нажатия клавиш
 * @param overriddenKeyPress — переопределяемые клавиши
 * @param hotKeyFromContextMenu - нажатая кнопка из контекстного меню, отвечающая за раскрытие узла
 * @param propagateSelection - рекурсивное выделение дочерних элементов при выборе родителя
 * @param onClickNodeInCheckboxMode - выбор узла в режиме checkbox
 */
const TreeComponent = ({
  sources,
  selectionMode,
  onSelectObject,
  searchText,
  affiliation,
  contextMenuRef,
  setNodeKey,
  externalSelectedObject,
  onDoubleNodeClick,
  onDragStart,
  onFinishTreeBuild,
  expandedObjects,
  onKeyPress,
  overriddenKeyPress,
  hotKeyFromContextMenu,
  propagateSelection = false,
  onClickNodeInCheckboxMode,
}: Props) => {
  /** Состояние для хранения объектов дерева */
  const [objects, setObjects] = useState<CustomTreeNode[]>();

  /** Состояние для хранения объектов дерева после фильтрации */
  const [filteredTree, setFilteredTree] = useState<CustomTreeNode[]>();

  /** Состояние для управления выбранными вершинами */
  const [selectedKeys, setSelectedKeys] = useState<string | TreeCheckboxSelectionKeys | null>(null);

  /** Состояние для управления вершиной, на которой было открыто меню */
  const [selectedNodeKey, setSelectedNodeKey] = useState<string | undefined>(undefined);

  /** Состояние для управления подсвеченной вершиной (для чекбокс режима) */
  const [highlightNode, setHighlightNode] = useState<string | undefined>(undefined);

  /** Состояние для управления развернутыми узлами */
  const [expandedKeys, setExpandedKeys] = useState<Record<string, boolean>>();
  const [externalKey, setExternalKey] = useState<string | null>(null);

  /** Ссылка для управления меню */
  const contentTreeRef = useRef<HTMLDivElement>(null);

  /** Хук для работы с деревом */
  const tree = useObjectTree();

  const expandedKeysBeforeFiltration = useRef<Record<string, boolean> | null>(null);

  /** Реф для каждого узла дерева (используется для прокрутки) */
  const nodeRefs = useRef<Record<string, HTMLDivElement | null>>({});

  /**
   * Выполняет восстановление состояния при монтировании компонента
   */
  useEffect(() => {
    nodeRefs.current = {};
    restoreScrollPosition();

    // Восстанавливаем раскрытые узлы
    const restoredKeys = affiliation && tree.restoreExpandedKeys(affiliation);
    if (restoredKeys) setExpandedKeys(restoredKeys);

    // Восстанавливаем состояние чекбоксов
    if (affiliation) {
      const savedSelectedKeys = localStorage.getItem(`selectedKeys${affiliation}`);
      if (savedSelectedKeys) {
        setSelectedKeys(JSON.parse(savedSelectedKeys));
      }
    }
  }, []);

  /**
   * Восстанавливает положение скролла после перезагрузки
   */
  const restoreScrollPosition = () => {
    const savedScrollPosition = affiliation ? localStorage.getItem(`scrollPosition${affiliation}`) : undefined;
    if (savedScrollPosition && contentTreeRef.current) {
      contentTreeRef.current.scrollTop = parseInt(savedScrollPosition, 10);
    }
  };

  /**
   * Восстанавливает положение скролла
   */
  useEffect(() => {
    restoreScrollPosition();
  }, [filteredTree]);

  /**
   * Осуществляет первоначальную инициализацию дерева
   */
  const initializeTree = useCallback(async () => {
    expandedKeysBeforeFiltration.current = {};
    setExpandedKeys({});

    if (sources.length > 0) {
      const newTree = await tree.buildTree(sources);
      if (!newTree) {
        onFinishTreeBuild?.();
        return;
      }
      setObjects(newTree);
      searchText && searchText?.length > 0
        ? setFilteredTree(tree.filterNodes(newTree, searchText))
        : setFilteredTree(newTree);
      restoreScrollPosition();
      onFinishTreeBuild?.();
    } else {
      onFinishTreeBuild?.();
      setFilteredTree([]);
    }
  }, [sources]);

  /**
   * Сворачивает узлы
   * @param nodes - узлы дерева
   */
  const collapseNodes = (nodes?: CustomTreeNode[]) => {
    return nodes?.map((node) => {
      node.expanded = false;

      if (node.children?.length) {
        collapseNodes(node.children);
      }
      return node;
    });
  };

  /**
   * Устанавливает узел выбранным
   */
  useEffect(() => {
    if (!externalSelectedObject) {
      return;
    }
    setSelectedKeys(externalSelectedObject);
    setExternalKey(externalSelectedObject);
  }, [externalSelectedObject]);

  /**
   * Разворачивает узлы
   */
  useEffect(() => {
    if (!expandedObjects) {
      return;
    }
    setExpandedKeys((expanded) => ({
      ...expanded,
      ...expandedObjects.reduce((acc, key) => ({ ...acc, [key]: true }), {}),
    }));
  }, [expandedObjects]);

  /**
   * Регистрация обработчика для нажатия клавиш
   */
  useEffect(() => {
    if (!overriddenKeyPress || overriddenKeyPress.length === 0) {
      return;
    }
    pressKeyHolder();
  }, []);

  useEffect(() => {
    if (!hotKeyFromContextMenu) {
      return;
    }

    handleKeyDown(hotKeyFromContextMenu);
  }, [hotKeyFromContextMenu]);
  /**
   * Обработчик нажатия клавиш
   */
  const pressKeyHolder = () => {
    const handleKeyPress = (event: KeyboardEvent) => {
      if (!overriddenKeyPress?.includes(event.key)) {
        return;
      }
      onKeyPress?.(event);
      event.stopPropagation();
      event.returnValue = false;
    };
    window.addEventListener('keydown', handleKeyPress);
    return () => {
      window.removeEventListener('keydown', handleKeyPress);
    };
  };

  /**
   * Обновляет состояние выбранных ключей, пересчитывая checked и partialChecked
   * @param nodes - узлы дерева
   * @param selectedKeys - текущие выбранные ключи
   */
  const updateSelectedKeys = (nodes: CustomTreeNode[], selectedKeys: TreeCheckboxSelectionKeys) => {
    const updatedKeys: TreeCheckboxSelectionKeys = { ...selectedKeys };

    const updateNode = (node: CustomTreeNode) => {
      const nodeKey = node.key as string;

      if (node.children && node.children.length > 0) {
        let allChecked = true;
        let someChecked = false;
        let somePartialChecked = false;

        node.children.forEach((child) => {
          updateNode(child);
          if (updatedKeys[child.key as string]?.checked) {
            someChecked = true;
          } else if (updatedKeys[child.key as string]?.partialChecked) {
            somePartialChecked = true;
          } else {
            allChecked = false;
          }
        });

        if (allChecked) {
          updatedKeys[nodeKey] = { checked: true, partialChecked: false };
        } else if (someChecked || somePartialChecked) {
          updatedKeys[nodeKey] = { checked: false, partialChecked: true };
        } else {
          delete updatedKeys[nodeKey];
        }
      }
    };

    nodes.forEach((node) => updateNode(node));
    return updatedKeys;
  };

  /**
   * Обрабатывает введенные данные в поиск
   * @param searchText - поисковый текст
   */
  const handleFilter = useCallback(
    (searchText: string) => {
      if (!objects) return;

      if (searchText?.length > 0) {
        if (objects) {
          const filteredTree = tree.filterNodes(objects, searchText);
          setFilteredTree(collapseNodes(filteredTree));

          // Обновляем состояние выбранных ключей
          setSelectedKeys(updateSelectedKeys(filteredTree, selectedKeys as TreeCheckboxSelectionKeys));
        }
      } else {
        setFilteredTree(collapseNodes(objects));

        // Обновляем состояние выбранных ключей
        setSelectedKeys(updateSelectedKeys(objects, selectedKeys as TreeCheckboxSelectionKeys));
      }
    },
    [searchText],
  );

  useEffect(() => {
    if (!affiliation || !expandedKeysBeforeFiltration || !expandedKeys) {
      return;
    }

    if (filteredTree && searchText && searchText?.length > 0) {
      const newExpandedKeys = getAllExpandedKeys(filteredTree, {}) ?? {};
      const previousKeysWithoutCurrentOnes = newExpandedKeys
        ? Object.keys(expandedKeys).filter((key) => !Object.keys(newExpandedKeys).includes(key))
        : null;

      expandedKeysBeforeFiltration.current =
        previousKeysWithoutCurrentOnes?.reduce(
          (acc, key) => {
            acc[key] = true;
            return acc;
          },
          {} as Record<string, boolean>,
        ) ?? {};

      setExpandedKeys(newExpandedKeys);
      tree.putExpandedKeysInLocalStorage({ ...newExpandedKeys, ...expandedKeysBeforeFiltration.current }, affiliation);
    } else {
      setExpandedKeys({ ...expandedKeys, ...expandedKeysBeforeFiltration.current });
      tree.putExpandedKeysInLocalStorage({ ...expandedKeys, ...expandedKeysBeforeFiltration.current }, affiliation);
    }
  }, [filteredTree]);

  /**
   * Возвращает коллекцию раскрытых ключей массива узлов
   * @param nodes - массив узлов
   * @param keys - коллекция раскрытых узлов
   */
  const getAllExpandedKeys = (nodes: CustomTreeNode[], keys: Record<string, boolean>) => {
    for (const node of nodes) {
      if (node.children && node.children.length > 0) {
        keys[node.key as string] = true;

        getAllExpandedKeys(node.children, keys);
      }
    }
    return keys;
  };

  useEffect(() => {
    initializeTree().catch(console.error);

    const restoredKeys = tree.restoreExpandedKeys(affiliation);
    if (restoredKeys) setExpandedKeys(restoredKeys);
  }, [initializeTree, sources]);

  /**
   * Обрабатывает введенные данные в поиск
   */
  useEffect(() => {
    if (!objects) return;

    if (searchText !== undefined) {
      handleFilter(searchText);
    }
  }, [handleFilter, objects, searchText]);

  const handleDragStart = (e: React.DragEvent<HTMLDivElement>) => {
    const node = (e.target as HTMLDivElement).querySelector('.nodeClass') as HTMLDivElement | null;

    if (node) {
      console.log(node.id);
      onDragStart?.(node.id, e);
    }
  };

  /**
   * Рендерит название узла
   * @param node
   */
  const nodeTemplate = (node: TreeNode) => {
    if (isCustomTreeNode(node)) {
      const className = `flex p-2 ml-2 border-round-sm text-left`;
      node.className = highlightNode === node.key ? 'highlight-node' : '';
      return (
        <div
          ref={(el) => {
            nodeRefs.current[node.key as string] = el;
            // Прокручиваем, если это выбранный программно узел
            if (node.key === externalKey && el) {
              el.scrollIntoView({ behavior: 'smooth', block: 'center' });
              setExternalKey(null);
            }
          }}
          className={className}
        >
          <div
            id={node.id}
            className={`${node.highlight ? 'bg-yellow-300' : ''} ${node.postFilterHighlight ? 'bg-blue-200' : ''} nodeClass`}
          >
            {' '}
            {node.label}
          </div>
        </div>
      );
    }
    return <></>;
  };

  /**
   * Запоминает раскрытые узлы
   * @param e
   */
  const onToggle = (e: TreeExpandedEvent) => {
    if (affiliation) {
      tree.putExpandedKeysInLocalStorage(e.value, affiliation);
    }
    setExpandedKeys(e.value);
  };

  /**
   * Запоминает состояние скролла
   */
  const handleScroll = () => {
    if (affiliation && contentTreeRef.current) {
      localStorage.setItem(`scrollPosition${affiliation}`, String(contentTreeRef.current.scrollTop?.toString()));
    }
  };

  /**
   * Устанавливает выбранные ключи узлов
   * @param e
   */
  const handleSelectionChange = (e: TreeSelectionEvent) => {
    if (!e.value) {
      return;
    }

    if (selectionMode === 'single') {
      setSelectedKeys(e.value as string);
      setSelectedNodeKey(e.value as string);
      onSelectObject(e.value as string);
    } else {
      const target = e.originalEvent.target as HTMLInputElement;
      //выбор по checkbox
      if (
        typeof target === 'object' &&
        Object.prototype.hasOwnProperty.call(target, 'type') &&
        target.type === 'checkbox'
      ) {
        setSelectedKeys(e.value as TreeCheckboxSelectionKeys);
        setSelectedNodeKey(e.value as string);
        onSelectObject(e.value as TreeCheckboxSelectionKeys);

        // Сохраняем выбранные ключи в localStorage
        if (affiliation) {
          localStorage.setItem(`selectedKeys${affiliation}`, JSON.stringify(e.value));
        }
      }
    }
  };

  /**
   * Устанавливает выбранный ключ по двойному щелчку
   * @param e
   */
  const handleDoubleClick = (e: TreeNodeDoubleClickEvent) => {
    if (e.node.key && selectionMode === 'single') {
      setSelectedKeys(e.node.key as string);
      onDoubleNodeClick?.(e.node.key as string);
    }
  };

  /**
   * Управляет нажатием по горячим клавишам
   * @param hotKey - парраметры нажатой клавиши
   */
  const handleKeyDown = (hotKey: HotKeyParams) => {
    if (!selectedNodeKey) return;

    //Обрабатывает команду РАСКРЫТИЯ коллекции дочерних элементов, выбранного узла
    if (hotKey.pressedCtrl && hotKey.key === HotKeys.ARROW_RIGHT) {
      setExpandedKeys((prevKeys) => ({
        ...prevKeys,
        [selectedNodeKey]: true,
      }));
      tree.putExpandedKeysInLocalStorage({ ...expandedKeys, [selectedNodeKey]: true }, affiliation);
    }
    //Обрабатывает команду СКРЫТИЯ коллекции дочерних элементов, выбранного узла
    if (hotKey.pressedCtrl && hotKey.key === HotKeys.ARROW_LEFT) {
      if (objects) {
        const selectedNode = tree.findNodeByKey(objects, selectedNodeKey);

        if (selectedNode && selectedNode.children) {
          const newExpandedKeys = { ...expandedKeys };

          selectedNode.children.forEach((node) => {
            tree.removeAllKeys(node, newExpandedKeys);
          });
          setExpandedKeys(newExpandedKeys);
          tree.putExpandedKeysInLocalStorage(newExpandedKeys, affiliation);
        }
      }
    }

    //Обрабатывает команду РАСКРЫТИЯ дочерних элементов, выбранного узла
    if (!hotKey.pressedCtrl && hotKey.key === HotKeys.ARROW_RIGHT) {
      if (objects) {
        const selectedNode = tree.findNodeByKey(objects, selectedNodeKey);

        if (selectedNode) {
          const allDescendantKeys = tree.getAllKeys(selectedNode);
          setExpandedKeys((prevExpandedKeys) => ({
            ...prevExpandedKeys,
            ...allDescendantKeys, // Добавляем ключи всех потомков
          }));
          tree.putExpandedKeysInLocalStorage({ ...expandedKeys, ...allDescendantKeys }, affiliation);
        }
      }
    }

    //Обрабатывает команду СКРЫТИЯ дочерних элементов, выбранного узла
    if (!hotKey.pressedCtrl && hotKey.key === HotKeys.ARROW_LEFT) {
      setExpandedKeys((prevKeys) => {
        const updatedKeys: Record<string, boolean> = { ...prevKeys };
        delete updatedKeys[selectedNodeKey];
        tree.putExpandedKeysInLocalStorage(updatedKeys, affiliation);
        return updatedKeys;
      });
    }
  };

  const onNodeClick = (e: TreeNodeClickEvent) => {
    const target = e.originalEvent.target as HTMLInputElement;
    //выбор по checkbox
    if (target.type === 'checkbox') {
      return;
    }

    if (onClickNodeInCheckboxMode) {
      onClickNodeInCheckboxMode(e.node.key as string);
    }

    setHighlightNode(e.node.key as string);
  };

  return (
    <div
      className='object-tree min-h-full max-h-full min-w-full flex flex-column flex-1 overflow-y-auto'
      ref={contentTreeRef}
      onScroll={handleScroll}
      onKeyDown={(e) => handleKeyDown({ pressedCtrl: e.ctrlKey, key: e.key })}
    >
      <Tree
        dragdropScope={affiliation === FieldName.OBJECTS ? 'editor' : undefined}
        pt={{
          node: { draggable: affiliation === FieldName.OBJECTS, onDragStart: handleDragStart },
        }}
        value={filteredTree}
        selectionMode={selectionMode}
        selectionKeys={selectedKeys}
        onSelectionChange={(e) => {
          handleSelectionChange(e);
        }}
        onNodeDoubleClick={(e: TreeNodeDoubleClickEvent) => {
          handleDoubleClick(e);
        }}
        onNodeClick={onNodeClick}
        emptyMessage='Результаты не найдены'
        className={`${selectionMode}-tree max-h-full md:w-full`}
        filter={false}
        filterMode='lenient'
        filterPlaceholder='Найти...'
        nodeTemplate={nodeTemplate}
        contextMenuSelectionKey={selectedNodeKey}
        onContextMenuSelectionChange={(e) => {
          setNodeKey?.(e.value as string);
          setSelectedNodeKey(e.value as string);
        }}
        onContextMenu={(e) => {
          contextMenuRef?.current?.show(e.originalEvent);
        }}
        expandedKeys={expandedKeys}
        onToggle={onToggle}
        propagateSelectionUp={propagateSelection}
        propagateSelectionDown={propagateSelection}
      />
    </div>
  );
};

export default TreeComponent;
и вот тако