diff --git a/drb-frontend/components/LiveView.tsx b/drb-frontend/components/LiveView.tsx index 95ff42d..3007867 100644 --- a/drb-frontend/components/LiveView.tsx +++ b/drb-frontend/components/LiveView.tsx @@ -284,9 +284,15 @@ export function LiveView() { lookupId && lookedUp && lookedUp.incident_id === lookupId && !!orgId && (lookedUp as IncidentDoc).org_id === orgId ? lookedUp : null; - const selected: IncidentDoc | null = activeSelected ?? fetchedSelected; const lookupPending = !!lookupId && !fetchedSelected && (lookupLoading || !orgId); const notFound = !!lookupId && !fetchedSelected && !lookupPending; + // The incident on screen resolving drops it from the active list before its + // by-id lookup lands. Hold the last doc for that same id meanwhile, so the + // wall doesn't fly back to the region and straight back again. + const lastSelected = useRef(null); + const held = lookupPending && lastSelected.current?.incident_id === link.inc ? lastSelected.current : null; + const selected: IncidentDoc | null = activeSelected ?? fetchedSelected ?? held; + lastSelected.current = selected; const resolvedSelected = fetchedSelected?.status === "resolved" ? fetchedSelected : null; // The selected incident's own calls, by incident id — no limit, so a long diff --git a/drb-frontend/lib/useIncidents.ts b/drb-frontend/lib/useIncidents.ts index be06281..ab4ff34 100644 --- a/drb-frontend/lib/useIncidents.ts +++ b/drb-frontend/lib/useIncidents.ts @@ -86,17 +86,19 @@ export function useIncidents(limitCount = 100, dateFrom?: Date, dateTo?: Date) { export function useIncident(incidentId: string | null) { const [incident, setIncident] = useState(null); const [loading, setLoading] = useState(true); + // The id the current `incident`/`loading` answer belongs to. On the render + // where the id changes the effect hasn't run yet, so `loading` alone would + // still read false from the previous id and a caller would flash "not found". + const [settledId, setSettledId] = useState(null); useEffect(() => { if (!incidentId) { setLoading(false); return; } - // A new id is a new lookup: without this, `loading` stays false from the - // previous id and a caller reads "not found" until the snapshot lands. setLoading(true); let unsubFirestore: (() => void) | undefined; const unsubAuth = onAuthStateChanged(auth, (user) => { if (unsubFirestore) { unsubFirestore(); unsubFirestore = undefined; } - if (!user) { setLoading(false); return; } + if (!user) { setLoading(false); setSettledId(incidentId); return; } const ref = doc(db, "incidents", incidentId); unsubFirestore = onSnapshot(ref, (snap) => { @@ -111,16 +113,19 @@ export function useIncident(incidentId: string | null) { setIncident(null); } setLoading(false); + setSettledId(incidentId); }, (err: FirestoreError) => { console.error("useIncident:", err); + setIncident(null); // a denied/failed read must not leave the previous id's doc setLoading(false); + setSettledId(incidentId); }); }); return () => { unsubAuth(); if (unsubFirestore) unsubFirestore(); }; }, [incidentId]); - return { incident, loading }; + return { incident, loading: loading || (!!incidentId && settledId !== incidentId) }; } export function useActiveIncidents() {