diff --git a/app/src/components/AnalystRatings.tsx b/app/src/components/AnalystRatings.tsx index 47b944e..d727ab7 100644 --- a/app/src/components/AnalystRatings.tsx +++ b/app/src/components/AnalystRatings.tsx @@ -52,17 +52,25 @@ export function AnalystRatings({ symbol }: Props) { const [consensus, setConsensus] = useState<{ strongBuy: number; buy: number; hold: number; sell: number; strongSell: number } | null>(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); + const [retryKey, setRetryKey] = useState(0); useEffect(() => { if (!symbol) return; setLoading(true); setError(null); + let cancelled = false; api.institutional.analystRatings(symbol).then((res) => { + if (cancelled) return; if (res.error) { setError(res.error); setRatings(null); } else { setRatings(res.ratings); setConsensus(res.consensus); } - }).catch((e: Error) => setError(e.message)) - .finally(() => setLoading(false)); - }, [symbol]); + }).catch((e: Error) => { + if (cancelled) return; + setError(e.message); + }).finally(() => { + if (!cancelled) setLoading(false); + }); + return () => { cancelled = true; }; + }, [symbol, retryKey]); const topFirms = useMemo(() => { if (!ratings) return []; @@ -76,7 +84,12 @@ export function AnalystRatings({ symbol }: Props) { if (!symbol) return null; if (loading) return
Loading analyst ratings...
; - if (error) return
Analyst ratings: {error}
; + if (error) return ( +
+ Analyst ratings: {error} + +
+ ); if (!ratings || ratings.length === 0) return
No analyst ratings available for {symbol}.
; const consensusText = consensus diff --git a/app/src/lib/trpc.ts b/app/src/lib/trpc.ts index 6b4a215..3deb593 100644 --- a/app/src/lib/trpc.ts +++ b/app/src/lib/trpc.ts @@ -396,6 +396,11 @@ async function trpcQuery(procedure: string, input?: unknown): Promise { signal: controller.signal, }); return parse(res); + } catch (e) { + if (e instanceof DOMException && e.name === "AbortError") { + throw new Error(`Request timed out (${TRPC_TIMEOUT_MS / 1000}s)`); + } + throw e; } finally { clearTimeout(timer); } @@ -413,6 +418,11 @@ async function trpcMutate(procedure: string, input: unknown): Promise { signal: controller.signal, }); return parse(res); + } catch (e) { + if (e instanceof DOMException && e.name === "AbortError") { + throw new Error(`Request timed out (${TRPC_TIMEOUT_MS / 1000}s)`); + } + throw e; } finally { clearTimeout(timer); }