11import { VirtualItem , Virtualizer , useVirtualizer } from "@tanstack/react-virtual" ;
22import { motion } from "framer-motion" ;
3- import { MutableRefObject , RefObject , useCallback , useEffect , useReducer , useRef } from "react" ;
3+ import {
4+ MutableRefObject ,
5+ RefObject ,
6+ useCallback ,
7+ useEffect ,
8+ useMemo ,
9+ useReducer ,
10+ useRef ,
11+ } from "react" ;
412import { cn } from "~/utils/cn" ;
513import { NodeState , NodesState , reducer } from "./reducer" ;
614import { concreteStateFromInput , selectedIdFromState } from "./utils" ;
@@ -47,6 +55,16 @@ export function TreeView<TData>({
4755
4856 const virtualItems = virtualizer . getVirtualItems ( ) ;
4957
58+ // id -> node lookup so each virtual row resolves in O(1) instead of
59+ // scanning the whole tree per row.
60+ const nodesById = useMemo ( ( ) => {
61+ const map = new Map < string , FlatTreeItem < TData > > ( ) ;
62+ for ( const node of tree ) {
63+ map . set ( node . id , node ) ;
64+ }
65+ return map ;
66+ } , [ tree ] ) ;
67+
5068 const scrollCallback = useCallback (
5169 ( event : Event ) => {
5270 if ( ! onScroll ) return ;
@@ -99,7 +117,7 @@ export function TreeView<TData>({
99117 } }
100118 >
101119 { virtualItems . map ( ( virtualItem ) => {
102- const node = tree . find ( ( node ) => node . id === virtualItem . key ) ;
120+ const node = nodesById . get ( virtualItem . key as string ) ;
103121 if ( ! node ) return null ;
104122 const state = nodes [ node . id ] ;
105123 if ( ! state ) return null ;
@@ -197,6 +215,16 @@ export function useTree<TData, TFilterValue>({
197215 concreteStateFromInput ( { tree, selectedId, collapsedIds, filter } )
198216 ) ;
199217
218+ // id -> index lookup so getNodeProps resolves in O(1) instead of scanning
219+ // the whole tree per rendered row.
220+ const treeIndexById = useMemo ( ( ) => {
221+ const map = new Map < string , number > ( ) ;
222+ tree . forEach ( ( node , index ) => {
223+ map . set ( node . id , index ) ;
224+ } ) ;
225+ return map ;
226+ } , [ tree ] ) ;
227+
200228 //sync external selectedId prop into internal state
201229 useEffect ( ( ) => {
202230 const internalSelectedId = selectedIdFromState ( state . nodes ) ;
@@ -497,7 +525,7 @@ export function useTree<TData, TFilterValue>({
497525 ( id : string ) => {
498526 const node = state . nodes [ id ] ;
499527 if ( ! node ) return { } ;
500- const treeItemIndex = tree . findIndex ( ( node ) => node . id === id ) ;
528+ const treeItemIndex = treeIndexById . get ( id ) ?? - 1 ;
501529 const treeItem = tree [ treeItemIndex ] ;
502530 return {
503531 "aria-expanded" : node . expanded ,
@@ -506,7 +534,7 @@ export function useTree<TData, TFilterValue>({
506534 tabIndex : node . selected ? - 1 : undefined ,
507535 } ;
508536 } ,
509- [ state ]
537+ [ state , treeIndexById ]
510538 ) ;
511539
512540 return {
0 commit comments