Skip to content

Commit 4517455

Browse files
committed
Use tanstack useQueries and add timeout
1 parent dcba10c commit 4517455

File tree

2 files changed

+82
-108
lines changed

2 files changed

+82
-108
lines changed

src/main.tsx

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,7 @@ import CashFlowPage from "./pages/balances/CashFlowPage"
1616
import { initKeycloak } from "./keycloak"
1717
import "./lib/api-client"
1818

19-
const queryClient = new QueryClient({
20-
defaultOptions: {
21-
queries: {
22-
enabled: (query) => query.queryKey[0] !== "balances",
23-
},
24-
},
25-
})
19+
const queryClient = new QueryClient()
2620

2721
const prepare = async () => {
2822
await initKeycloak()

src/pages/balances/BalancesPage.tsx

Lines changed: 81 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import { PropsWithChildren, Suspense, useCallback, useEffect, useState } from "react"
1+
import { PropsWithChildren, Suspense } from "react"
2+
import { useQueries } from "@tanstack/react-query"
23
import { Breadcrumbs } from "@/components/Breadcrumbs"
34
import { PageTitle } from "@/components/PageTitle"
45
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
@@ -7,6 +8,15 @@ import { type ChartConfig, ChartContainer, ChartLegend, ChartLegendContent } fro
78
import { Skeleton } from "@/components/ui/skeleton"
89
import { debitBalance, creditBalance, onchainBalance } from "@/generated/client/sdk.gen"
910

11+
function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
12+
return Promise.race([
13+
promise,
14+
new Promise<T>((_, reject) =>
15+
setTimeout(() => reject(new Error(`Request timeout after ${timeoutMs}ms`)), timeoutMs),
16+
),
17+
])
18+
}
19+
1020
function Loader() {
1121
return (
1222
<div className="flex flex-col gap-4 my-2">
@@ -134,110 +144,88 @@ export function BalanceText({ amount, unit, children }: PropsWithChildren<Balanc
134144
)
135145
}
136146

137-
function isErrorResponse<T>(res: { data?: T; error?: unknown }): res is { data: undefined; error: unknown } {
138-
return res.error !== undefined
139-
}
140-
141147
function useBalances() {
142-
const [balances, setBalances] = useState<Record<string, BalanceDisplay>>({
143-
bitcoin: { amount: "0", unit: "BTC" },
144-
eiou: { amount: "0", unit: "eIOU" },
145-
credit: { amount: "0", unit: "credit" },
146-
debit: { amount: "0", unit: "debit" },
147-
})
148-
const [isLoading, setIsLoading] = useState(false)
149-
const [error, setError] = useState<string | null>(null)
150-
151-
const fetchAllBalances = useCallback(async () => {
152-
setIsLoading(true)
153-
setError(null)
154-
155-
try {
156-
// Fetch both credit and debit balances concurrently
157-
const [creditResponse, debitResponse, onchainResponse] = await Promise.allSettled([
158-
creditBalance({}),
159-
debitBalance({}),
160-
onchainBalance({}),
161-
])
162-
163-
const newBalances: Record<string, BalanceDisplay> = {
164-
bitcoin: { amount: "0", unit: "BTC" },
165-
eiou: { amount: "0", unit: "eIOU" },
166-
credit: { amount: "0", unit: "credit" },
167-
debit: { amount: "0", unit: "debit" },
168-
}
169-
170-
// Handle credit balance response
171-
if (creditResponse.status === "fulfilled" && !isErrorResponse(creditResponse.value)) {
172-
const creditData = creditResponse.value.data
173-
if (creditData && "amount" in creditData && "unit" in creditData) {
174-
newBalances.credit = {
175-
amount: String(creditData.amount),
176-
unit: String(creditData.unit),
148+
const queries = useQueries({
149+
queries: [
150+
{
151+
queryKey: ["balance", "credit"],
152+
queryFn: async () => {
153+
const response = await withTimeout(creditBalance({}), 10_000)
154+
if (response.error) {
155+
throw new Error("Failed to fetch credit balance")
177156
}
178-
}
179-
} else {
180-
console.warn(
181-
"Failed to fetch credit balance:",
182-
creditResponse.status === "rejected" ? creditResponse.reason : creditResponse.value.error,
183-
)
184-
}
185-
186-
// Handle debit balance response
187-
if (debitResponse.status === "fulfilled" && !isErrorResponse(debitResponse.value)) {
188-
const debitData = debitResponse.value.data
189-
if (debitData && "amount" in debitData && "unit" in debitData) {
190-
newBalances.debit = {
191-
amount: String(debitData.amount),
192-
unit: String(debitData.unit),
157+
return response.data
158+
},
159+
refetchInterval: 30_000,
160+
staleTime: 25_000,
161+
retry: 2,
162+
gcTime: 10_000,
163+
},
164+
{
165+
queryKey: ["balance", "debit"],
166+
queryFn: async () => {
167+
const response = await withTimeout(debitBalance({}), 10_000)
168+
if (response.error) {
169+
throw new Error("Failed to fetch debit balance")
193170
}
194-
}
195-
} else {
196-
console.warn(
197-
"Failed to fetch debit balance:",
198-
debitResponse.status === "rejected" ? debitResponse.reason : debitResponse.value.error,
199-
)
200-
}
201-
202-
if (onchainResponse.status === "fulfilled" && !isErrorResponse(onchainResponse.value)) {
203-
const onchainData = onchainResponse.value.data
204-
if (onchainData && typeof onchainData === "object" && "confirmed" in onchainData) {
205-
console.log("Onchain balance:", onchainData.confirmed)
206-
newBalances.bitcoin = {
207-
amount: String(onchainData.confirmed),
208-
unit: "BTC",
171+
return response.data
172+
},
173+
refetchInterval: 30_000,
174+
staleTime: 25_000,
175+
retry: 2,
176+
gcTime: 10_000,
177+
},
178+
{
179+
queryKey: ["balance", "onchain"],
180+
queryFn: async () => {
181+
const response = await withTimeout(onchainBalance({}), 10_000)
182+
if (response.error) {
183+
throw new Error("Failed to fetch onchain balance")
209184
}
210-
}
211-
} else {
212-
console.warn(
213-
"Failed to fetch onchain balance:",
214-
onchainResponse.status === "rejected" ? onchainResponse.reason : onchainResponse.value.error,
215-
)
216-
}
185+
return response.data
186+
},
187+
refetchInterval: 30_000,
188+
staleTime: 25_000,
189+
retry: 2,
190+
gcTime: 10_000,
191+
},
192+
],
193+
})
217194

218-
setBalances(newBalances)
219-
} catch (error) {
220-
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"
221-
setError(errorMessage)
222-
console.error("Failed to fetch balances:", error)
223-
} finally {
224-
setIsLoading(false)
225-
}
226-
}, [])
195+
const [creditQuery, debitQuery, onchainQuery] = queries
227196

228-
useEffect(() => {
229-
void fetchAllBalances()
197+
const isPending = queries.some((query) => query.isPending)
198+
const hasError = queries.some((query) => query.isError)
199+
const error = hasError ? "Failed to load one or more balances" : null
230200

231-
const interval = setInterval(() => void fetchAllBalances(), 30_000)
201+
const balances: Record<string, BalanceDisplay> = {
202+
bitcoin: {
203+
amount:
204+
onchainQuery.data && typeof onchainQuery.data === "object" && "confirmed" in onchainQuery.data
205+
? String(onchainQuery.data.confirmed)
206+
: "0",
207+
unit: "BTC",
208+
},
209+
eiou: { amount: "0", unit: "eIOU" },
210+
credit: {
211+
amount: creditQuery.data && "amount" in creditQuery.data ? String(creditQuery.data.amount) : "0",
212+
unit: creditQuery.data && "unit" in creditQuery.data ? String(creditQuery.data.unit) : "credit",
213+
},
214+
debit: {
215+
amount: debitQuery.data && "amount" in debitQuery.data ? String(debitQuery.data.amount) : "0",
216+
unit: debitQuery.data && "unit" in debitQuery.data ? String(debitQuery.data.unit) : "debit",
217+
},
218+
}
232219

233-
return () => clearInterval(interval)
234-
}, [fetchAllBalances])
220+
const refetch = () => {
221+
void Promise.all(queries.map((query) => query.refetch()))
222+
}
235223

236-
return { balances, isLoading, error, refetch: fetchAllBalances }
224+
return { balances, isPending, error, refetch }
237225
}
238226

239227
function PageBodyWithDevSection() {
240-
const { balances, isLoading, error } = useBalances()
228+
const { balances, isPending, error } = useBalances()
241229

242230
if (error) {
243231
return (
@@ -253,14 +241,6 @@ function PageBodyWithDevSection() {
253241
)
254242
}
255243

256-
if (isLoading) {
257-
return (
258-
<>
259-
<Loader />
260-
</>
261-
)
262-
}
263-
264244
return (
265245
<>
266246
<div className="flex flex-col gap-4 my-2">

0 commit comments

Comments
 (0)