import { useCallback, useEffect, useRef, useState } from 'react'; import { api } from '../../../utils/api'; import type { Project } from '../../../types/app'; import type { FileTreeNode } from '../types/types'; type UseFileTreeDataResult = { files: FileTreeNode[]; loading: boolean; refreshFiles: () => void; }; export function useFileTreeData(selectedProject: Project | null): UseFileTreeDataResult { const [files, setFiles] = useState([]); const [loading, setLoading] = useState(false); const [refreshKey, setRefreshKey] = useState(0); const abortControllerRef = useRef(null); const refreshFiles = useCallback(() => { setRefreshKey((prev) => prev + 1); }, []); useEffect(() => { // File-tree requests use the DB projectId; the backend resolves it to the // project's absolute path through the projects table. const projectId = selectedProject?.projectId; if (!projectId) { setFiles([]); setLoading(false); return; } // Abort previous request if (abortControllerRef.current) { abortControllerRef.current.abort(); } abortControllerRef.current = new AbortController(); // Track mount state so aborted or late responses do not enqueue stale state updates. let isActive = true; const fetchFiles = async () => { if (isActive) { setLoading(true); } try { const response = await api.getFiles(projectId, { signal: abortControllerRef.current!.signal }); if (!response.ok) { const errorText = await response.text(); console.error('File fetch failed:', response.status, errorText); if (isActive) { setFiles([]); } return; } const data = (await response.json()) as FileTreeNode[]; if (isActive) { setFiles(data); } } catch (error) { if ((error as { name?: string }).name === 'AbortError') { return; } console.error('Error fetching files:', error); if (isActive) { setFiles([]); } } finally { if (isActive) { setLoading(false); } } }; void fetchFiles(); return () => { isActive = false; abortControllerRef.current?.abort(); }; }, [selectedProject?.projectId, refreshKey]); return { files, loading, refreshFiles, }; }