Files
nexcurrency/hooks/use-market-rates.ts

83 lines
2.1 KiB
TypeScript

"use client";
import { useCallback, useEffect, useMemo, useState } from "react";
import { buildApiUrl } from "@/lib/api/url";
import { parseRatesResponse, RatesResponse } from "@/lib/rates";
const REFRESH_INTERVAL_MS = 60_000;
export function useMarketRates() {
const [data, setData] = useState<RatesResponse | null>(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(true);
const fetchRates = useCallback(async () => {
try {
const response = await fetch(buildApiUrl("/api/rates"));
if (!response.ok) {
const payload = (await response.json().catch(() => null)) as {
message?: string;
} | null;
throw new Error(
payload?.message ?? "Unable to fetch latest market rates",
);
}
const payload = await response.json();
const parsed = parseRatesResponse(payload);
const isFallback = response.headers.get("X-Cache-Fallback") === "stale-on-error";
const fallbackError = response.headers.get("X-Cache-Fallback-Error");
setData(parsed);
setError(isFallback ? (fallbackError ?? "Upstream provider error") : null);
} catch (err) {
const message = err instanceof Error ? err.message : "Unknown error";
setError(message);
} finally {
setIsLoading(false);
}
}, []);
useEffect(() => {
void fetchRates();
const tick = () => {
if (document.visibilityState === "visible") {
void fetchRates();
}
};
const id = window.setInterval(() => {
tick();
}, REFRESH_INTERVAL_MS);
const handleVisibilityChange = () => {
if (document.visibilityState === "visible") {
void fetchRates();
}
};
document.addEventListener("visibilitychange", handleVisibilityChange);
return () => {
window.clearInterval(id);
document.removeEventListener("visibilitychange", handleVisibilityChange);
};
}, [fetchRates]);
const state = useMemo(
() => ({
data,
error,
isLoading,
refresh: fetchRates,
}),
[data, error, isLoading, fetchRates],
);
return state;
}