Files
Mav-Mobile-UI/src/hooks/useSightingFeed.ts

92 lines
2.6 KiB
TypeScript
Raw Normal View History

2025-08-20 08:27:05 +01:00
import { useEffect, useMemo, useRef, useState } from "react";
import type { SightingWidgetType } from "../types/types";
export function useSightingFeed(
baseUrl: string,
{
limit = 7,
pollMs = 800,
autoSelectLatest = true,
}: {
limit?: number;
pollMs?: number;
autoSelectLatest?: boolean;
} = {}
) {
const [items, setItems] = useState<SightingWidgetType[]>(
() => Array(limit).fill(null) as unknown as SightingWidgetType[]
);
const [selectedRef, setSelectedRef] = useState<number | null>(null);
const [mostRecent, setMostRecent] = useState<SightingWidgetType | null>(null);
const mostRecentRef = useRef<number>(-1);
// effective selected (fallback to most recent)
const selected = useMemo(
() =>
selectedRef == null
? null
: items.find((x) => x?.ref === selectedRef) ?? null,
[items, selectedRef]
);
const effectiveSelected = selected ?? mostRecent ?? null;
useEffect(() => {
let delay = pollMs;
let dead = false;
const controller = new AbortController();
async function tick() {
try {
// Pause when tab hidden to save CPU/network
if (document.hidden) {
setTimeout(tick, Math.max(delay, 2000));
return;
}
const url = `http://100.116.253.81/mergedHistory/sightingSummary?mostRecentRef=${mostRecentRef.current}`;
const res = await fetch(url, { signal: controller.signal });
if (!res.ok) throw new Error(String(res.status));
const obj: SightingWidgetType = await res.json();
if (obj && typeof obj.ref === "number" && obj.ref > -1) {
setItems((prev) => {
const next = [obj, ...prev].slice(0, limit);
// maintain selection if still present; otherwise select newest if allowed
const stillExists =
selectedRef != null && next.some((x) => x?.ref === selectedRef);
if (autoSelectLatest && !stillExists) {
setSelectedRef(obj.ref);
}
return next;
});
setMostRecent(obj);
mostRecentRef.current = obj.ref;
delay = pollMs; // reset backoff on success
}
} catch {
// exponential backoff (max 10s)
delay = Math.min(delay * 2, 10000);
} finally {
if (!dead) setTimeout(tick, delay);
}
}
const t = setTimeout(tick, pollMs);
return () => {
dead = true;
controller.abort();
clearTimeout(t);
};
}, [baseUrl, limit, pollMs, autoSelectLatest, selectedRef]);
return {
items,
selectedRef,
setSelectedRef,
mostRecent,
effectiveSelected,
mostRecentRef,
};
}