syco.me Homelab Dashboard

This commit is contained in:
2026-05-10 21:23:42 +02:00
parent 933e492d15
commit 90de2c1674
45 changed files with 6666 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
import { useState, useEffect, useCallback } from 'react'
interface ApiState<T> {
data: T | null
loading: boolean
error: string | null
lastUpdated: Date | null
}
export function useApi<T>(url: string, intervalMs = 30_000): ApiState<T> {
const [state, setState] = useState<ApiState<T>>({
data: null,
loading: true,
error: null,
lastUpdated: null,
})
const hasData = state.data !== null
const fetch_ = useCallback(async () => {
try {
const res = await fetch(url)
if (!res.ok) {
const body = await res.json().catch(() => ({ error: res.statusText }))
throw new Error(body.error ?? res.statusText)
}
const data: T = await res.json()
setState({ data, loading: false, error: null, lastUpdated: new Date() })
} catch (err: unknown) {
setState(prev => ({
...prev,
loading: false,
error: err instanceof Error ? err.message : 'Unknown error',
}))
}
}, [url])
useEffect(() => {
fetch_()
// Retry every 3s while we have no data yet (startup race / transient error),
// then slow down to the normal interval once data is flowing.
const id = setInterval(fetch_, hasData ? intervalMs : 3_000)
return () => clearInterval(id)
}, [fetch_, intervalMs, hasData])
return state
}