({
dataProvider,
refreshKey,
isExpandable = defaultIsExpandable,
autoExpandDepth = 3,
}: UseDiscoverTreeOptions)
| 42 | * the returned state into `<DiscoverPanel>`. |
| 43 | */ |
| 44 | export function useDiscoverTree({ |
| 45 | dataProvider, |
| 46 | refreshKey, |
| 47 | isExpandable = defaultIsExpandable, |
| 48 | autoExpandDepth = 3, |
| 49 | }: UseDiscoverTreeOptions): UseDiscoverTreeResult { |
| 50 | const [roots, setRoots] = useState<TreeNodeData[]>([]); |
| 51 | const [loading, setLoading] = useState(true); |
| 52 | const [expanded, setExpanded] = useState<Set<string>>(new Set()); |
| 53 | const [childrenMap, setChildrenMap] = useState<Map<string, TreeNodeData[]>>( |
| 54 | new Map(), |
| 55 | ); |
| 56 | const [loadingSet, setLoadingSet] = useState<Set<string>>(new Set()); |
| 57 | |
| 58 | // Refs for latest values — used by async effects to avoid stale closures |
| 59 | const rootsRef = useRef(roots); |
| 60 | rootsRef.current = roots; |
| 61 | const childrenMapRef = useRef(childrenMap); |
| 62 | childrenMapRef.current = childrenMap; |
| 63 | const expandedRef = useRef(expanded); |
| 64 | expandedRef.current = expanded; |
| 65 | |
| 66 | // O(1) node-type lookup map, rebuilt when roots/childrenMap change |
| 67 | const nodeTypeMapRef = useRef(new Map<string, string>()); |
| 68 | nodeTypeMapRef.current = (() => { |
| 69 | const m = new Map<string, string>(); |
| 70 | for (const r of roots) m.set(r.id, r.type); |
| 71 | for (const children of childrenMap.values()) { |
| 72 | for (const c of children) m.set(c.id, c.type); |
| 73 | } |
| 74 | return m; |
| 75 | })(); |
| 76 | |
| 77 | /** Fetch children and write them into state. */ |
| 78 | const loadChildren = useCallback( |
| 79 | async (nodeId: string, nodeType: string): Promise<TreeNodeData[]> => { |
| 80 | const children = await dataProvider.fetchChildren(nodeId, nodeType); |
| 81 | setChildrenMap((prev) => { |
| 82 | const next = new Map(prev); |
| 83 | next.set(nodeId, children); |
| 84 | return next; |
| 85 | }); |
| 86 | return children; |
| 87 | }, |
| 88 | [dataProvider], |
| 89 | ); |
| 90 | |
| 91 | // Load roots on mount (and re-load when refreshKey changes), then auto-expand |
| 92 | useEffect(() => { |
| 93 | let cancelled = false; |
| 94 | async function init() { |
| 95 | setChildrenMap(new Map()); |
| 96 | try { |
| 97 | const fetchedRoots = await dataProvider.fetchRoots(); |
| 98 | if (cancelled) return; |
| 99 | setRoots(fetchedRoots); |
| 100 | |
| 101 | // Auto-expand N levels (root → children → grandchildren → ...) |
no test coverage detected