Skip to content

Commit b9817f3

Browse files
committed
refactor: replace fetch-in-useEffect with useQuery for repo list
Added useRepos hook to useCachedQuery.ts: - Uses React Query with refetchInterval: 30s (replaces setInterval) - Request deduplication, caching, loading/error states for free - Returns invalidate() function for manual refresh after mutations DashboardHome.tsx: - Removed manual useState for repos and reposLoading - Removed fetchRepos function and its useEffect + setInterval - Replaced all fetchRepos() calls with refreshRepos() (invalidate) - Reduced component by ~20 lines of manual data fetching code Closes OPE-30
1 parent 8464527 commit b9817f3

6 files changed

Lines changed: 251 additions & 28 deletions

File tree

frontend/bun.lock

Lines changed: 184 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

frontend/package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"dev": "vite --host",
88
"build": "vite build",
99
"typecheck": "tsc --noEmit",
10+
"test": "vitest run",
11+
"test:watch": "vitest",
1012
"preview": "vite preview"
1113
},
1214
"dependencies": {
@@ -48,13 +50,18 @@
4850
"tailwindcss-animate": "^1.0.7"
4951
},
5052
"devDependencies": {
53+
"@testing-library/jest-dom": "^6.9.1",
54+
"@testing-library/react": "^16.3.2",
55+
"@testing-library/user-event": "^14.6.1",
5156
"@types/react": "^18.2.43",
5257
"@types/react-dom": "^18.2.17",
5358
"@vitejs/plugin-react": "^4.7.0",
5459
"autoprefixer": "^10.4.16",
60+
"jsdom": "^28.1.0",
5561
"postcss": "^8.4.32",
5662
"tailwindcss": "^3.4.0",
5763
"typescript": "^5.9.3",
58-
"vite": "^6.4.1"
64+
"vite": "^6.4.1",
65+
"vitest": "^4.0.18"
5966
}
6067
}

frontend/src/components/dashboard/DashboardHome.tsx

Lines changed: 5 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { UpgradeLimitModal } from '../UpgradeLimitModal'
3030
import type { Repository } from '../../types'
3131
import type { GitHubRepo } from '../../hooks/useGitHubRepos'
3232
import { API_URL } from '../../config/api'
33+
import { useRepos, useInvalidateRepoCache } from '../../hooks/useCachedQuery'
3334

3435
const MAX_FREE_REPOS = 3
3536

@@ -72,11 +73,10 @@ type RepoTab = 'overview' | 'search' | 'dependencies' | 'insights' | 'impact'
7273
export function DashboardHome() {
7374
const { session } = useAuth()
7475
const [searchParams, setSearchParams] = useSearchParams()
75-
const [repos, setRepos] = useState<Repository[]>([])
76+
const { data: repos = [], isLoading: reposLoading, invalidate: refreshRepos } = useRepos(session?.access_token)
7677
const [selectedRepo, setSelectedRepo] = useState<string | null>(null)
7778
const [activeTab, setActiveTab] = useState<RepoTab>('overview')
7879
const [loading, setLoading] = useState(false)
79-
const [reposLoading, setReposLoading] = useState(true)
8080
const [showAddForm, setShowAddForm] = useState(false)
8181
const [showGitHubSelector, setShowGitHubSelector] = useState(false)
8282

@@ -103,27 +103,6 @@ export function DashboardHome() {
103103
}
104104
}, [searchParams, setSearchParams])
105105

106-
const fetchRepos = async () => {
107-
if (!session?.access_token) return
108-
try {
109-
const response = await fetch(`${API_URL}/repos`, {
110-
headers: { 'Authorization': `Bearer ${session.access_token}` }
111-
})
112-
const data = await response.json()
113-
setRepos(data.repositories || [])
114-
} catch (error) {
115-
console.error('Error fetching repos:', error)
116-
} finally {
117-
setReposLoading(false)
118-
}
119-
}
120-
121-
useEffect(() => {
122-
fetchRepos()
123-
const interval = setInterval(fetchRepos, 30000)
124-
return () => clearInterval(interval)
125-
}, [session])
126-
127106
const handleAddRepo = async (gitUrl: string, branch: string) => {
128107
try {
129108
setLoading(true)
@@ -170,7 +149,7 @@ export function DashboardHome() {
170149
setShowIndexingModal(true)
171150
setShowAddForm(false)
172151

173-
await fetchRepos()
152+
refreshRepos()
174153
} catch (error) {
175154
console.error('Error adding repo:', error)
176155
toast.error('Failed to add repository', { description: error instanceof Error ? error.message : 'Please check the Git URL and try again' })
@@ -245,11 +224,11 @@ export function DashboardHome() {
245224
}
246225

247226
setLoading(false)
248-
await fetchRepos()
227+
refreshRepos()
249228
}
250229

251230
const handleIndexingComplete = async () => {
252-
await fetchRepos()
231+
refreshRepos()
253232
toast.success('Indexing complete!', { description: `${indexingRepoName} is ready for search` })
254233
}
255234

frontend/src/hooks/useCachedQuery.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,35 @@ export function useInvalidateRepoCache() {
165165
invalidateRepoCache(repoId)
166166
}
167167
}
168+
169+
/**
170+
* Fetch repos list with 30s auto-refresh.
171+
* Replaces manual fetch-in-useEffect + setInterval pattern.
172+
*/
173+
export function useRepos(apiKey: string | undefined) {
174+
const queryClient = useQueryClient()
175+
176+
const query = useQuery({
177+
queryKey: ['repos'],
178+
queryFn: async () => {
179+
const data = await fetchWithAuth(`${API_URL}/repos`, apiKey!)
180+
return (data.repositories || []) as Array<{
181+
id: string
182+
name: string
183+
url: string
184+
status: string
185+
file_count: number
186+
language: string
187+
branch: string
188+
created_at: string
189+
}>
190+
},
191+
enabled: !!apiKey,
192+
refetchInterval: 30_000,
193+
staleTime: 10_000,
194+
})
195+
196+
const invalidate = () => queryClient.invalidateQueries({ queryKey: ['repos'] })
197+
198+
return { ...query, invalidate }
199+
}

frontend/src/test/setup.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
// global test setup -- extends expect with DOM matchers
2+
import '@testing-library/jest-dom'

frontend/vitest.config.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/// <reference types="vitest" />
2+
import { defineConfig } from 'vitest/config'
3+
import react from '@vitejs/plugin-react'
4+
import path from 'path'
5+
6+
export default defineConfig({
7+
plugins: [react()],
8+
test: {
9+
globals: true,
10+
environment: 'jsdom',
11+
setupFiles: ['./src/test/setup.ts'],
12+
include: ['src/**/*.{test,spec}.{ts,tsx}'],
13+
css: false,
14+
},
15+
resolve: {
16+
alias: {
17+
'@': path.resolve(__dirname, './src'),
18+
},
19+
},
20+
})

0 commit comments

Comments
 (0)